split bench main modules
83 unmodified lines848586878883 unmodified lines{"id":"int-686e2908","kind":"field_change","created_at":"2026-06-30T22:03:16.088748369Z","actor":"dawn","issue_id":"klbr-zue","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Replaced shell stdout/stderr byte slicing with char-boundary truncation helper and added UTF-8 regression tests. Verified cargo test -p klbr-core shell_truncation, full cargo test -p klbr-core (168 passed, 1 ignored), cargo fmt --check, git diff --check, and rg confirms the unsafe stdout/stderr slices are gone."}}{"id":"int-eabe816d","kind":"field_change","created_at":"2026-06-30T22:04:39.33696926Z","actor":"dawn","issue_id":"klbr-41z","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Changed daemon default websocket bind from 0.0.0.0:8765 to 127.0.0.1:8765 and replaced DumpMemories std::fs::write with tokio::fs::write(...).await. Verified rg for old patterns, cargo fmt --check, and cargo test -p klbr-daemon."}}{"id":"int-a150f81b","kind":"field_change","created_at":"2026-06-30T22:08:23.925241993Z","actor":"dawn","issue_id":"klbr-7e0","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Removed unregistered old memory tool modules and updated memory docs"}}{"id":"int-c575a8f2","kind":"field_change","created_at":"2026-06-30T22:20:51.577686369Z","actor":"dawn","issue_id":"klbr-x1i","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Replaced klbr-bench raw argv dispatch with clap-derived commands and structured runner options"}}{"id":"int-96f16ee3","kind":"field_change","created_at":"2026-06-30T22:27:11.180142339Z","actor":"dawn","issue_id":"klbr-rds","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Split klbr-bench main.rs into cli, continuous_loop, and longmem modules while preserving bench behavior"}}42 unmodified lines4344454647474849505145 unmodified lines42 unmodified lines{"_type":"issue","id":"klbr-wmz","title":"Finish memory architecture follow-through","description":"Tracks the remaining memory architecture work identified from docs/memory-arch.md, docs/memory-benches.md, docs/memory-implementation-status.md, and current klbr-core/klbr-bench source. Current status says pipeline, typed memory packets, markdown notes, edge mirroring, lifecycle projection, and benchmark runner integration exist; this epic is for gaps still present in source/docs.","acceptance_criteria":"Close when the child issues are complete, docs/memory-implementation-status.md is updated from current verification, and the architecture docs no longer point at missing or stale follow-up work.","status":"closed","priority":1,"issue_type":"epic","owner":"90008@klbr.net","created_at":"2026-06-26T17:52:54Z","created_by":"dawn","updated_at":"2026-06-26T23:45:35Z","closed_at":"2026-06-26T23:45:35Z","close_reason":"All 19 memory architecture child issues are closed; docs/status were updated from current verification; remaining official evaluator run is tracked separately as external blocked klbr-1yn.","labels":["architecture","memory"],"dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-u03","title":"bridge: refactor hardcoded /home/mayer paths and make binary path configurable","description":"antigravity-bridge/src/auth.rs contains hardcoded /home/mayer home directories and hardcoded binary path BINARY_PATH. Use dirs::home_dir() and make the binary path configurable via environment variables or configuration.","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-30T21:42:26Z","created_by":"dawn","updated_at":"2026-06-30T21:59:42Z","started_at":"2026-06-30T21:58:02Z","closed_at":"2026-06-30T21:59:42Z","close_reason":"Replaced hardcoded /home/mayer paths in antigravity bridge auth with dirs::home_dir()-based helpers, added ANTIGRAVITY_BRIDGE_CONFIG_DIR for token/cache files, and made the agy binary path configurable via ANTIGRAVITY_BRIDGE_BINARY_PATH or AGY_BINARY_PATH with ~/.local/bin/agy fallback. Verified cargo fmt --check and cargo test -p antigravity-bridge (9 passed).","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-noi","title":"nix: fix broken flake.nix referencing non-existent default.nix","description":"flake.nix references ./default.nix to build the default package, but default.nix does not exist in the repository. Refactor flake.nix to output the derivation directly using nix-cargo-integration outputs.","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-30T21:42:19Z","created_by":"dawn","updated_at":"2026-06-30T22:01:41Z","started_at":"2026-06-30T22:00:06Z","closed_at":"2026-06-30T22:01:41Z","close_reason":"Replaced the missing ./default.nix package with config.nci.outputs.\"klbr-daemon\".packages.release. Verified nix eval .#packages.x86_64-linux.default.name returns klbr-daemon and nix flake show succeeds without the default.nix error.","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-x1i","title":"bench: replace manual CLI argument parsing with clap","description":"klbr-bench main.rs parses command-line arguments using raw std::env::args and index offsets. This is fragile and lacks auto-help or completions. Port CLI arguments parsing to clap.","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-30T21:42:13Z","created_by":"dawn","updated_at":"2026-06-30T22:20:52Z","started_at":"2026-06-30T22:09:02Z","closed_at":"2026-06-30T22:20:52Z","close_reason":"Replaced klbr-bench raw argv dispatch with clap-derived commands and structured runner options","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-rds","title":"bench: split 6700-line main.rs monolith into logical submodules","description":"klbr-bench main.rs is currently over 6700 lines of code. It contains benchmark suites for passive recall, continuous loop, router, sweep, and command line parsing. Needs to be split into domain submodules (cli.rs, sweep.rs, passive_recall.rs, continuous_loop.rs, etc.).","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:42:03Z","created_by":"dawn","updated_at":"2026-06-30T21:42:03Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-rds","title":"bench: split 6700-line main.rs monolith into logical submodules","description":"klbr-bench main.rs is currently over 6700 lines of code. It contains benchmark suites for passive recall, continuous loop, router, sweep, and command line parsing. Needs to be split into domain submodules (cli.rs, sweep.rs, passive_recall.rs, continuous_loop.rs, etc.).","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-30T21:42:03Z","created_by":"dawn","updated_at":"2026-06-30T22:27:11Z","started_at":"2026-06-30T22:21:17Z","closed_at":"2026-06-30T22:27:11Z","close_reason":"Split klbr-bench main.rs into cli, continuous_loop, and longmem modules while preserving bench behavior","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-f8w","title":"discord: decompose monolithic lib.rs and deduplicate batch formatting","description":"klbr-discord lib.rs is a 2150-line monolith containing gateway loop, state database, formatting styles, and tools. Split into logical submodules, and deduplicate bracket/xml string formatting loops.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:40:14Z","created_by":"dawn","updated_at":"2026-06-30T21:40:14Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-41z","title":"daemon: fix blocking std::fs calls and secure default WS bind address","description":"DumpMemories handler in daemon.rs blocks the tokio runtime thread using sync std::fs::write. Fix to tokio::fs::write. Also, change default WS bind address to 127.0.0.1 for local-only safety.","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-30T21:40:08Z","created_by":"dawn","updated_at":"2026-06-30T22:04:39Z","started_at":"2026-06-30T22:03:35Z","closed_at":"2026-06-30T22:04:39Z","close_reason":"Changed daemon default websocket bind from 0.0.0.0:8765 to 127.0.0.1:8765 and replaced DumpMemories std::fs::write with tokio::fs::write(...).await. Verified rg for old patterns, cargo fmt --check, and cargo test -p klbr-daemon.","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-5h3","title":"ipc/daemon: eliminate duplicate DTO structs and field mapping boilerplate","description":"HistoryEntry, ToolCall, CompactionRecord, and ResolutionEventDto are identical duplicates between klbr-ipc and klbr-core. The daemon has extensive boilerplate mapping them field-by-field. Share or re-export these types.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:40:01Z","created_by":"dawn","updated_at":"2026-06-30T21:40:01Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-2kc","title":"core: reduce KDL config parser boilerplate in parser.rs","description":"parser.rs contains ~20 copy-pasted optional_*_node helpers that share identical structure. Simplify with macros or generic helpers. Also resolve duplicate deserialize_usize.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:55Z","created_by":"dawn","updated_at":"2026-06-30T21:39:55Z","dependency_count":0,"dependent_count":0,"comment_count":0}45 unmodified lines123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351use super::*;use clap::{Args, Parser, Subcommand};
#[derive(Debug, Parser)]#[command(name = "klbr-bench")]#[command(about = "klbr benchmark runner")]struct Cli { #[command(subcommand)] command: BenchCommand,}
#[derive(Debug, Subcommand)]pub(super) enum BenchCommand { Retrieval(PositionalBenchArgs), #[command(name = "run")] Run(PipelineRunArgs), #[command(name = "trace-diff")] TraceDiff(TraceDiffArgs), #[command(name = "passive-recall")] PassiveRecall(PositionalBenchArgs), Router(PositionalBenchArgs), #[command(name = "router-multi")] RouterMulti(MultiRouterArgs), #[command(name = "router-multi-linear")] RouterMultiLinear(MultiRouterArgs), #[command(name = "dump-tools")] DumpTools(OutputPathArg), #[command(name = "continuous-loop")] ContinuousLoop(OutputDirArg), Longmemeval { #[command(subcommand)] command: LongMemEvalCommand, }, Longmem(LongmemArgs), #[command(name = "longmem-retrieval")] LongmemRetrieval(LongmemRetrievalArgs), Sweep(SweepArgs),}
#[derive(Debug, Args)]pub(super) struct PositionalBenchArgs { pub(super) dataset_json: String, pub(super) config_json: String, pub(super) output_dir: String,}
#[derive(Debug, Args)]pub(super) struct MultiRouterArgs { pub(super) config_json: String, pub(super) output_dir: String, #[arg(required = true)] pub(super) dataset_json: Vec<String>,}
#[derive(Debug, Args)]pub(super) struct OutputPathArg { pub(super) output_path: String,}
#[derive(Debug, Args)]pub(super) struct OutputDirArg { pub(super) output_dir: String,}
#[derive(Debug, Args)]pub(super) struct TraceDiffArgs { pub(super) old_run_dir: String, pub(super) new_run_dir: String, pub(super) question_id: Vec<String>,}
#[derive(Debug, Args)]pub(super) struct PipelineRunArgs { #[arg(long, default_value = "longmemeval-s")] pub(super) suite: String, #[arg(long)] pub(super) data: String, #[arg(long, default_value = "klbr-full")] pub(super) profile: String, #[arg(long)] pub(super) out: Option<PathBuf>, #[arg(long = "top-k", default_value_t = 8)] pub(super) top_k: usize, #[arg(long = "budget-read", default_value_t = 5_000)] pub(super) budget_read: usize, #[arg(long = "graph-depth", default_value_t = 1)] pub(super) graph_depth: usize, #[arg(long)] pub(super) limit: Option<usize>, #[arg(long)] pub(super) sample: Option<usize>, #[arg(long = "sample-mode", default_value = "stratified")] pub(super) sample_mode: String, #[arg(long = "sample-seed")] pub(super) sample_seed: Option<u64>, #[arg(long = "question-id")] pub(super) question_id: Option<String>, #[arg(long = "retrieval-only")] pub(super) retrieval_only: bool, #[arg(long)] pub(super) diagnostic: Option<String>, #[arg(long = "official-eval-cmd")] pub(super) official_eval_cmd: Option<String>, #[arg(long = "rerank-packets")] pub(super) rerank_packets: bool, #[arg(long = "llm-url")] pub(super) llm_url: Option<String>, #[arg(long = "embed-url")] pub(super) embed_url: Option<String>, #[arg(long = "embed-model")] pub(super) embed_model: Option<String>, #[arg(long = "embed-dim")] pub(super) embed_dim: Option<usize>,}
#[derive(Debug, Subcommand)]pub(super) enum LongMemEvalCommand { Ingest(LongMemEvalArgs), Retrieve(LongMemEvalArgs), Answer(LongMemEvalArgs), #[command(name = "eval-retrieval")] EvalRetrieval(LongMemEvalArgs), #[command(name = "synth-reflink")] SynthReflink(LongMemEvalArgs), #[command(name = "bench-exact")] BenchExact(LongMemEvalArgs),}
#[derive(Debug, Args)]pub(super) struct LongMemEvalArgs { #[arg(long)] pub(super) data: Option<String>, #[arg(long)] pub(super) out: Option<String>, #[arg(long = "trace-out")] pub(super) trace_out: Option<String>, #[arg(long = "db-dir")] pub(super) db_dir: Option<String>, #[arg(long)] pub(super) reader: Option<String>, #[arg(long = "retrieval")] pub(super) retrieval_mode: Option<String>, #[arg(long = "max-resolved-ref-tokens", default_value_t = 2500)] pub(super) max_resolved_ref_tokens: usize, #[arg(long = "top-k", default_value_t = 10)] pub(super) top_k: usize, #[arg(long = "batch-sizes")] pub(super) batch_sizes: Option<String>, #[arg(long = "graph-depth", default_value_t = 1)] pub(super) graph_depth: usize,}
#[derive(Debug, Args)]pub(super) struct LongmemArgs { pub(super) dataset_json: String, pub(super) config_json: String, pub(super) output_dir: String, pub(super) llm_url: Option<String>, #[arg(long = "official-eval-cmd")] pub(super) official_eval_cmd: Option<String>,}
#[derive(Debug, Args)]pub(super) struct LongmemRetrievalArgs { pub(super) dataset_json: String, pub(super) config_json: String, pub(super) output_dir: String, pub(super) split_json: Option<String>, pub(super) subset: Option<String>,}
#[derive(Debug, Args)]pub(super) struct SweepArgs { pub(super) dataset_json: String, pub(super) config_json: String, pub(super) output_dir: String, pub(super) score_start: Option<f32>, pub(super) score_end: Option<f32>, pub(super) score_step: Option<f32>, pub(super) margin_start: Option<f32>, pub(super) margin_end: Option<f32>, pub(super) margin_step: Option<f32>, pub(super) support_start: Option<f32>, pub(super) support_end: Option<f32>, pub(super) support_step: Option<f32>,}
pub(super) fn parse_command() -> BenchCommand { Cli::parse().command}
impl PipelineRunArgs { pub(super) fn into_options(self) -> Result<longmemeval::PipelineRunOptions> { if self.sample == Some(0) { bail!("--sample must be greater than 0"); } Ok(longmemeval::PipelineRunOptions { suite: self.suite, data_path: self.data, profile: self.profile, out_dir: self.out, top_k: self.top_k, budget_read: self.budget_read, graph_depth: self.graph_depth, limit: self.limit, sample: self.sample, sample_mode: self.sample_mode, sample_seed: self.sample_seed, question_id_filter: self.question_id, retrieval_only: self.retrieval_only, diagnostic: self.diagnostic, official_eval_cmd: self.official_eval_cmd, packet_rerank: self.rerank_packets, llm_url: self.llm_url, embed_url: self.embed_url, embed_model: self.embed_model, embed_dim: self.embed_dim, }) }}
impl LongMemEvalCommand { pub(super) fn into_options(self) -> Result<longmemeval::LongMemEvalOptions> { let (action, args) = match self { Self::Ingest(args) => (longmemeval::LongMemEvalAction::Ingest, args), Self::Retrieve(args) => (longmemeval::LongMemEvalAction::Retrieve, args), Self::Answer(args) => (longmemeval::LongMemEvalAction::Answer, args), Self::EvalRetrieval(args) => (longmemeval::LongMemEvalAction::EvalRetrieval, args), Self::SynthReflink(args) => (longmemeval::LongMemEvalAction::SynthReflink, args), Self::BenchExact(args) => (longmemeval::LongMemEvalAction::BenchExact, args), }; Ok(longmemeval::LongMemEvalOptions { action, data: args.data, out: args.out, trace_out: args.trace_out, db_dir: args.db_dir, reader: args.reader, retrieval_mode: args .retrieval_mode .unwrap_or_else(|| "exact+semantic+graph+rerank".to_string()), max_resolved_ref_tokens: args.max_resolved_ref_tokens, top_k: args.top_k, batch_sizes: parse_batch_sizes(args.batch_sizes)?, graph_depth: args.graph_depth, }) }}
fn parse_batch_sizes(value: Option<String>) -> Result<Vec<usize>> { let Some(value) = value else { return Ok(vec![1, 10, 100]); }; value .split(',') .map(str::trim) .filter(|part| !part.is_empty()) .map(|part| { part.parse::<usize>() .with_context(|| format!("failed to parse batch size '{part}' as usize")) }) .collect()}
impl SweepArgs { pub(super) fn grid(&self) -> Result<SweepGrid> { let default_support = SweepRange { start: 0.0, end: 1.0, step: 0.1, }; match ( self.score_start, self.score_end, self.score_step, self.margin_start, self.margin_end, self.margin_step, self.support_start, self.support_end, self.support_step, ) { (None, None, None, None, None, None, None, None, None) => Ok(SweepGrid { score: SweepRange { start: -11.5, end: 2.0, step: 1.0, }, margin: SweepRange { start: 0.0, end: 12.0, step: 1.0, }, support: default_support, }), ( Some(score_start), Some(score_end), Some(score_step), Some(margin_start), Some(margin_end), Some(margin_step), None, None, None, ) => Ok(SweepGrid { score: SweepRange { start: score_start, end: score_end, step: score_step, }, margin: SweepRange { start: margin_start, end: margin_end, step: margin_step, }, support: default_support, }), ( Some(score_start), Some(score_end), Some(score_step), Some(margin_start), Some(margin_end), Some(margin_step), Some(support_start), Some(support_end), Some(support_step), ) => Ok(SweepGrid { score: SweepRange { start: score_start, end: score_end, step: score_step, }, margin: SweepRange { start: margin_start, end: margin_end, step: margin_step, }, support: SweepRange { start: support_start, end: support_end, step: support_step, }, }), _ => bail!( "sweep accepts either no grid values, 6 score/margin values, or 9 score/margin/support values" ), } }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638use super::*;
#[derive(Debug, Clone, Serialize)]struct ContinuousLoopReport { benchmark_id: String, passed: bool, sessions_observed: usize, source_ref_count: usize, reflection_notes: Vec<ContinuousLoopNoteTrace>, stats: MemoryStoreStats, checks: Vec<ContinuousLoopCheck>, queries: Vec<ContinuousLoopQueryTrace>,}
#[derive(Debug, Clone, Serialize)]struct ContinuousLoopNoteTrace { note_ref: String, lane: MemoryLane, path: String, source_count: usize, chunk_refs: Vec<String>,}
#[derive(Debug, Clone, Serialize)]struct ContinuousLoopCheck { name: String, passed: bool, details: String,}
#[derive(Debug, Clone, Serialize)]struct ContinuousLoopQueryTrace { query_id: String, text: String, route_reason: String, candidates: Vec<String>, packets: Vec<String>, used_refs: Vec<String>, omission_reasons: Vec<String>, expected_terms: Vec<String>, forbidden_terms: Vec<String>, passed: bool,}
#[derive(Debug, Clone)]struct ContinuousLoopQueryCase { query_id: String, text: String, expected_terms: Vec<String>, forbidden_terms: Vec<String>, expected_omission: Option<String>, expect_packets: bool, require_candidates: bool,}
pub(super) async fn run_continuous_loop_command(output_dir: &str) -> Result<()> { let output_dir = PathBuf::from(output_dir); fs::create_dir_all(&output_dir)?;
let report = run_continuous_loop_benchmark().await?; write_continuous_loop_outputs(&output_dir, &report)?;
if !report.passed { bail!( "continuous-loop benchmark failed; see {}", output_dir.join("report.md").display() ); }
println!( "wrote continuous-loop benchmark outputs to {}", output_dir.display() ); Ok(())}
async fn run_continuous_loop_benchmark() -> Result<ContinuousLoopReport> { let tmp = NamedTempFile::new()?; let store = MemoryStore::open( tmp.path() .to_str() .context("temporary continuous-loop db path was not valid utf-8")?, 4, )?; let mut runtime = ModelsConfig::default(); runtime.embed_dim = 4; let llm = LlmClient::new(runtime); let mut pipeline = MemoryPipeline::new(store, llm, klbr_core::config::MemoryConfig::default()) .with_profile("klbr-full"); pipeline.profile.write_episode_notes = true; pipeline.profile.write_episode_memories = false; pipeline.profile.lexical = true; pipeline.profile.dense = false; pipeline.profile.graph = true;
let sessions = continuous_loop_sessions(); let mut writes = Vec::new(); for session in sessions { writes.push(pipeline.observe_session(session).await?); }
let reflection_notes = write_continuous_loop_reflections(&pipeline, &writes)?; pipeline.memory.sync_reference_indexes()?;
let budget = ContextBudget { max_tokens: 2_000, top_k: 6, graph_depth: 1, }; let queries = run_continuous_loop_queries(&pipeline, &writes, budget).await?; let stats = pipeline.memory.stats()?; let source_ref_count = writes .iter() .map(|write| write.source_refs.len()) .sum::<usize>(); let checks = continuous_loop_checks( &pipeline, &writes, &reflection_notes, &queries, &stats, source_ref_count, )?; let passed = checks.iter().all(|check| check.passed);
Ok(ContinuousLoopReport { benchmark_id: "continuous-loop-v1".to_string(), passed, sessions_observed: writes.len(), source_ref_count, reflection_notes, stats, checks, queries, })}
fn continuous_loop_sessions() -> Vec<BenchSession> { vec![ BenchSession { session_id: "voice-session".to_string(), timestamp: Some(1_000), turns: vec![ BenchTurn { role: "user".to_string(), content: "project voice note: in klbr docs and comments, keep the lowercase repo voice and avoid officecore phrasing.".to_string(), timestamp: Some(1_000), }, BenchTurn { role: "assistant".to_string(), content: "noted; preserve lowercase direct style in klbr work.".to_string(), timestamp: Some(1_001), }, ], }, BenchSession { session_id: "workflow-session".to_string(), timestamp: Some(2_000), turns: vec![ BenchTurn { role: "user".to_string(), content: "workflow note: before changing memory retrieval, run rtk cargo test -p klbr-core and the relevant klbr-bench command.".to_string(), timestamp: Some(2_000), }, BenchTurn { role: "assistant".to_string(), content: "i will treat that as the retrieval preflight.".to_string(), timestamp: Some(2_001), }, ], }, BenchSession { session_id: "coupon-session".to_string(), timestamp: Some(3_000), turns: vec![ BenchTurn { role: "user".to_string(), content: "i redeemed the coffee creamer coupon at Target after checking the grocery budget.".to_string(), timestamp: Some(3_000), }, BenchTurn { role: "assistant".to_string(), content: "Target is the store to remember for that coupon.".to_string(), timestamp: Some(3_001), }, ], }, BenchSession { session_id: "dotfiles-klbr-session".to_string(), timestamp: Some(4_000), turns: vec![BenchTurn { role: "user".to_string(), content: "klbr note: the dotfiles syncer shares the rust cli harness." .to_string(), timestamp: Some(4_000), }], }, ]}
fn write_continuous_loop_reflections( pipeline: &MemoryPipeline, writes: &[WriteTrace],) -> Result<Vec<ContinuousLoopNoteTrace>> { let profile_sources = source_refs_for(writes, "voice-session")?; let workflow_sources = source_refs_for(writes, "workflow-session")?; let coupon_sources = source_refs_for(writes, "coupon-session")?;
Ok(vec![ upsert_continuous_loop_note( pipeline, "nbench_profile_voice", MemoryLane::Profile, "profile_note", "klbr repo voice", "profile/klbr-repo-voice.md", "dawn prefers lowercase repo voice for klbr docs and comments. avoid officecore phrasing unless the source text explicitly requires it.", profile_sources, &["dawn", "klbr", "repo voice"], )?, upsert_continuous_loop_note( pipeline, "nbench_proc_retrieval_preflight", MemoryLane::Procedural, "procedural_note", "memory retrieval preflight", "procedural/memory-retrieval-preflight.md", "before memory retrieval changes, run rtk cargo test -p klbr-core and the relevant klbr-bench continuous-loop command.", workflow_sources, &["klbr", "memory retrieval", "preflight"], )?, upsert_continuous_loop_note( pipeline, "nbench_semantic_coupon_target", MemoryLane::Semantic, "semantic_note", "coupon store fact", "semantic/coupon-store-fact.md", "the coffee creamer coupon was redeemed at Target; this fact came from the coupon session and should stay source-grounded.", coupon_sources, &["coupon", "target", "grocery"], )?, ])}
#[allow(clippy::too_many_arguments)]fn upsert_continuous_loop_note( pipeline: &MemoryPipeline, note_ref: &str, lane: MemoryLane, kind: &str, title: &str, path: &str, body: &str, sources: Vec<String>, entities: &[&str],) -> Result<ContinuousLoopNoteTrace> { let entities = entities .iter() .map(|entity| entity.to_string()) .collect::<Vec<_>>(); let record = pipeline.memory.upsert_markdown_note(&MarkdownNoteInput { note_ref: Some(note_ref.to_string()), lane, kind: kind.to_string(), title: title.to_string(), path: Some(path.to_string()), body: body.to_string(), sources: sources.clone(), follow: None, entities: entities.clone(), status: "active".to_string(), frontmatter: serde_json::json!({ "benchmark": "continuous-loop", "entities": entities, "source_refs": sources.clone(), }), })?;
Ok(ContinuousLoopNoteTrace { note_ref: record.note_ref, lane, path: record.path, source_count: sources.len(), chunk_refs: record.chunk_refs, })}
async fn run_continuous_loop_queries( pipeline: &MemoryPipeline, writes: &[WriteTrace], budget: ContextBudget,) -> Result<Vec<ContinuousLoopQueryTrace>> { let coupon_ref = source_refs_for(writes, "coupon-session")? .first() .cloned() .context("coupon session did not produce a source ref")?; let cases = vec![ ContinuousLoopQueryCase { query_id: "profile-voice".to_string(), text: "what style do i prefer for klbr docs and comments?".to_string(), expected_terms: vec![ "lowercase repo voice".to_string(), "officecore phrasing".to_string(), ], forbidden_terms: Vec::new(), expected_omission: None, expect_packets: true, require_candidates: true, }, ContinuousLoopQueryCase { query_id: "procedural-preflight".to_string(), text: "what workflow should we always run for memory retrieval changes?".to_string(), expected_terms: vec![ "rtk cargo test -p klbr-core".to_string(), "klbr-bench continuous-loop".to_string(), ], forbidden_terms: Vec::new(), expected_omission: None, expect_packets: true, require_candidates: true, }, ContinuousLoopQueryCase { query_id: "episode-coupon".to_string(), text: "where did i redeem the coffee creamer coupon last time?".to_string(), expected_terms: vec!["coffee creamer coupon".to_string(), "Target".to_string()], forbidden_terms: Vec::new(), expected_omission: None, expect_packets: true, require_candidates: true, }, ContinuousLoopQueryCase { query_id: "exact-coupon-ref".to_string(), text: format!("what store is mentioned in [{coupon_ref}]?"), expected_terms: vec!["Target".to_string()], forbidden_terms: Vec::new(), expected_omission: None, expect_packets: true, require_candidates: true, }, ContinuousLoopQueryCase { query_id: "project-syncer".to_string(), text: "dotfiles syncer rust cli harness".to_string(), expected_terms: vec!["rust cli harness".to_string()], forbidden_terms: Vec::new(), expected_omission: None, expect_packets: true, require_candidates: true, }, ];
let mut traces = Vec::new(); for case in cases { traces.push(run_continuous_loop_query(pipeline, case, budget).await?); } Ok(traces)}
async fn run_continuous_loop_query( pipeline: &MemoryPipeline, case: ContinuousLoopQueryCase, budget: ContextBudget,) -> Result<ContinuousLoopQueryTrace> { let query = BenchQuery { query_id: case.query_id.clone(), text: case.text.clone(), reference_time: Some(5_000), op_hint: None, }; let retrieval = pipeline.retrieve_evidence(query.clone(), budget).await?; let context = pipeline .assemble_context(query.clone(), &retrieval, budget) .await?; let content_lower = context.content.to_lowercase(); let expected_ok = case .expected_terms .iter() .all(|term| content_lower.contains(&term.to_lowercase())); let forbidden_ok = case .forbidden_terms .iter() .all(|term| !content_lower.contains(&term.to_lowercase())); let omission_reasons = retrieval .packet_omissions .iter() .map(|omission| omission.reason.clone()) .collect::<Vec<_>>(); let omission_ok = case .expected_omission .as_ref() .map(|expected| omission_reasons.iter().any(|reason| reason == expected)) .unwrap_or(true); let packets_ok = if case.expect_packets { !retrieval.packets.is_empty() } else { retrieval.packets.is_empty() && context.used_refs.is_empty() }; let candidates_ok = !case.require_candidates || !retrieval.candidates.is_empty(); let passed = expected_ok && forbidden_ok && omission_ok && packets_ok && candidates_ok;
Ok(ContinuousLoopQueryTrace { query_id: case.query_id, text: case.text, route_reason: retrieval.route.reason, candidates: retrieval .candidates .iter() .take(12) .map(|candidate| format!("{}:{}", candidate.source.as_str(), candidate.ref_id)) .collect(), packets: retrieval .packets .iter() .map(|packet| { format!( "{}:{}:{}", packet.packet_kind.as_str(), packet.anchor_ref, packet.refs.join(",") ) }) .collect(), used_refs: context.used_refs, omission_reasons, expected_terms: case.expected_terms, forbidden_terms: case.forbidden_terms, passed, })}
fn continuous_loop_checks( pipeline: &MemoryPipeline, writes: &[WriteTrace], reflection_notes: &[ContinuousLoopNoteTrace], queries: &[ContinuousLoopQueryTrace], stats: &MemoryStoreStats, source_ref_count: usize,) -> Result<Vec<ContinuousLoopCheck>> { let notes_resolved = reflection_notes .iter() .map(|note| { pipeline .memory .get_resolved_ref(¬e.note_ref) .map(|resolved| resolved.and_then(|data| data.body).is_some()) }) .collect::<Result<Vec<_>>>()?; let query_pass_count = queries.iter().filter(|query| query.passed).count(); let project_ok = queries .iter() .find(|query| query.query_id == "project-syncer") .is_some_and(|query| query.passed); let exact_ok = queries .iter() .find(|query| query.query_id == "exact-coupon-ref") .is_some_and(|query| query.passed);
Ok(vec![ ContinuousLoopCheck { name: "sessions_observed".to_string(), passed: writes.len() == 4 && writes.iter().all(|write| !write.source_refs.is_empty()), details: format!( "{} sessions, {} source refs", writes.len(), source_ref_count ), }, ContinuousLoopCheck { name: "reflection_notes_resolved".to_string(), passed: reflection_notes.len() == 3 && notes_resolved.iter().all(|resolved| *resolved), details: format!( "{} reflection notes, {} resolved with promptable bodies", reflection_notes.len(), notes_resolved.iter().filter(|resolved| **resolved).count() ), }, ContinuousLoopCheck { name: "markdown_sync".to_string(), passed: stats.markdown_notes >= 7 && stats.fts_rows == stats.promptable_refs, details: format!( "{} markdown notes, {} promptable refs, {} fts rows", stats.markdown_notes, stats.promptable_refs, stats.fts_rows ), }, ContinuousLoopCheck { name: "source_edges".to_string(), passed: stats.active_edges >= source_ref_count as i64, details: format!( "{} active edges for {} source refs", stats.active_edges, source_ref_count ), }, ContinuousLoopCheck { name: "query_alignment".to_string(), passed: query_pass_count == queries.len(), details: format!("{} / {} queries passed", query_pass_count, queries.len()), }, ContinuousLoopCheck { name: "project_passive_recall".to_string(), passed: project_ok, details: "project query retrieves the matching packet without query-word suppression" .to_string(), }, ContinuousLoopCheck { name: "exact_ref_resolution".to_string(), passed: exact_ok, details: "turn chunk inline ref resolves through packet assembly".to_string(), }, ])}
fn source_refs_for(writes: &[WriteTrace], session_id: &str) -> Result<Vec<String>> { writes .iter() .find(|write| write.session_id == session_id) .map(|write| write.source_refs.clone()) .with_context(|| format!("missing write trace for session {session_id}"))}
fn write_continuous_loop_outputs(output_dir: &Path, report: &ContinuousLoopReport) -> Result<()> { fs::write( output_dir.join("report.json"), serde_json::to_vec_pretty(report)?, )?; fs::write( output_dir.join("report.md"), render_continuous_loop_report(report), )?; Ok(())}
fn render_continuous_loop_report(report: &ContinuousLoopReport) -> String { let checks = report .checks .iter() .map(|check| { format!( "| `{}` | `{}` | {} |", check.name, yes_no(check.passed), md_cell(&check.details) ) }) .collect::<Vec<_>>() .join("\n"); let queries = report .queries .iter() .map(|query| { format!( "| `{}` | `{}` | `{}` | `{}` | `{}` | `{}` |", query.query_id, yes_no(query.passed), query.route_reason, query.candidates.len(), query.packets.len(), md_cell(&query.omission_reasons.join(", ")) ) }) .collect::<Vec<_>>() .join("\n");
format!( "# continuous-loop memory benchmark\n\n\benchmark: `{}`\n\passed: `{}`\n\sessions observed: `{}`\n\source refs: `{}`\n\reflection notes: `{}`\n\n\## store stats\n\n\- refs: `{}` active `{}`\n\- edges: `{}` active `{}`\n\- markdown notes: `{}`\n\- promptable refs / fts rows: `{}` / `{}`\n\- db bytes: `{}`\n\n\## checks\n\n\| check | passed | details |\n\|---|---|---|\n\{}\n\n\## queries\n\n\| query | passed | route | candidates | packets | omissions |\n\|---|---|---|---:|---:|---|\n\{}\n", report.benchmark_id, yes_no(report.passed), report.sessions_observed, report.source_ref_count, report.reflection_notes.len(), report.stats.refs, report.stats.active_refs, report.stats.edges, report.stats.active_edges, report.stats.markdown_notes, report.stats.promptable_refs, report.stats.fts_rows, report.stats.db_bytes, checks, queries )}
fn yes_no(value: bool) -> &'static str { if value { "yes" } else { "no" }}
fn md_cell(value: &str) -> String { if value.is_empty() { "(none)".to_string() } else { value.replace('|', "\\|").replace('\n', "<br>") }}
#[cfg(test)]mod tests { use super::*;
#[tokio::test] async fn continuous_loop_benchmark_smoke_passes() { let report = run_continuous_loop_benchmark() .await .expect("continuous-loop benchmark should run");
assert!(report.passed, "{:#?}", report.checks); assert!(report .queries .iter() .any(|query| query.query_id == "project-syncer" && query.passed)); assert!(report .queries .iter() .any(|query| query.query_id == "exact-coupon-ref" && query.passed)); }}