From 448f43999e757c5941e9b09da5444910621ce6d4 Mon Sep 17 00:00:00 2001 From: ebigunso Date: Fri, 16 Oct 2026 04:56:04 +0900 Subject: [PATCH 1/6] Split oversized LongMemEval turns while preserving source identity --- Cargo.lock | 1 + crates/cmem-eval-longmemeval/Cargo.toml | 1 + crates/cmem-eval-longmemeval/src/identity.rs | 19 ++- crates/cmem-eval-longmemeval/src/ingest.rs | 28 ++-- crates/cmem-eval-longmemeval/src/lib.rs | 11 ++ crates/cmem-eval-longmemeval/src/loader.rs | 138 +++++++++++++++++- crates/cmem-eval-longmemeval/src/types.rs | 60 ++++++++ .../tests/observation_splits.rs | 109 ++++++++++++++ crates/cmem-eval-runner/src/pipeline.rs | 12 ++ .../tests/longmemeval_observation_splits.rs | 93 ++++++++++++ crates/cmem-eval/src/results.rs | 27 ++++ 11 files changed, 479 insertions(+), 20 deletions(-) create mode 100644 crates/cmem-eval-longmemeval/tests/observation_splits.rs create mode 100644 crates/cmem-eval-runner/tests/longmemeval_observation_splits.rs diff --git a/Cargo.lock b/Cargo.lock index 9786ffa9..0922f32f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -883,6 +883,7 @@ dependencies = [ "serde", "serde_json", "tempfile", + "tiktoken-rs", "tracing", ] diff --git a/crates/cmem-eval-longmemeval/Cargo.toml b/crates/cmem-eval-longmemeval/Cargo.toml index 3e7b8c98..08cb7d5f 100644 --- a/crates/cmem-eval-longmemeval/Cargo.toml +++ b/crates/cmem-eval-longmemeval/Cargo.toml @@ -9,6 +9,7 @@ chrono.workspace = true serde.workspace = true serde_json.workspace = true tracing.workspace = true +tiktoken-rs.workspace = true cmem-eval = { path = "../cmem-eval" } [dev-dependencies] diff --git a/crates/cmem-eval-longmemeval/src/identity.rs b/crates/cmem-eval-longmemeval/src/identity.rs index 61466d6f..fbf9b182 100644 --- a/crates/cmem-eval-longmemeval/src/identity.rs +++ b/crates/cmem-eval-longmemeval/src/identity.rs @@ -38,13 +38,24 @@ impl Identities { *ordinal += 1; identities.session_ids.insert(assigned.clone(), raw.clone()); for turn in &session.turns { - identities.turn_ids.insert( - format!("{assigned}:turn:{}", turn.index), - format!("{raw}:turn:{}", turn.index), - ); + for (part, _) in turn.observation_texts().enumerate() { + identities.turn_ids.insert( + observation_id(&assigned, turn.index, part), + format!("{raw}:turn:{}", turn.index), + ); + } } identities.episodes.push(assigned); } identities } } + +pub(crate) fn observation_id(episode: &str, turn: usize, part: usize) -> String { + let original = format!("{episode}:turn:{turn}"); + if part == 0 { + original + } else { + format!("{original}:part:{}", part + 1) + } +} diff --git a/crates/cmem-eval-longmemeval/src/ingest.rs b/crates/cmem-eval-longmemeval/src/ingest.rs index 65f50b0a..83d620d4 100644 --- a/crates/cmem-eval-longmemeval/src/ingest.rs +++ b/crates/cmem-eval-longmemeval/src/ingest.rs @@ -42,19 +42,21 @@ pub fn to_memory_inputs(instance: &LongMemEvalInstance) -> LongMemEvalMemoryInpu }), }); for turn in &session.turns { - observations.push(ObservationInput { - external_id: format!("{}:turn:{}", assigned, turn.index), - episode_external_id: assigned.clone(), - namespace: namespace.clone(), - speaker: turn.speaker.clone(), - text: turn.text.clone(), - observed_at: session.date.clone(), - metadata: serde_json::json!({ - "source": "longmemeval_s", - "raw_date": session.raw_date.clone(), - "normalized_date": session.date.clone() - }), - }); + for (part, text) in turn.observation_texts().enumerate() { + observations.push(ObservationInput { + external_id: crate::identity::observation_id(assigned, turn.index, part), + episode_external_id: assigned.clone(), + namespace: namespace.clone(), + speaker: turn.speaker.clone(), + text: text.to_string(), + observed_at: session.date.clone(), + metadata: serde_json::json!({ + "source": "longmemeval_s", + "raw_date": session.raw_date.clone(), + "normalized_date": session.date.clone() + }), + }); + } } } diff --git a/crates/cmem-eval-longmemeval/src/lib.rs b/crates/cmem-eval-longmemeval/src/lib.rs index fd57df56..03f53019 100644 --- a/crates/cmem-eval-longmemeval/src/lib.rs +++ b/crates/cmem-eval-longmemeval/src/lib.rs @@ -32,6 +32,17 @@ //! raw ID. Scoring recomputes the same identity tables from the item to map both //! session and turn rankings back to benchmark IDs, crediting each ID only at //! its first retrieved rank. Retrieved row IDs are assigned; gold IDs are raw. +//! +//! Oversized turns retain their original text/index but gain observation parts +//! at greedy sentence/blank-paragraph boundaries, each native prefixed surface +//! at most 8192 cl100k tokens. If none fits, use the last newline, then the last +//! whitespace under the limit; an unbreakable oversized span is an admission +//! error. The manifest records each part's boundary kind; source end closes its +//! final paragraph. +//! Ingest keeps the original ID for the first part and uses `:part:2`, ... for +//! later parts in the same episode/speaker/time. Scoring maps every part to the +//! original turn; full-history text and converter source text stay unchanged. +//! Conventional run headers record affected identities and the split policy. mod error; mod identity; diff --git a/crates/cmem-eval-longmemeval/src/loader.rs b/crates/cmem-eval-longmemeval/src/loader.rs index 2b23efb1..c3e7108b 100644 --- a/crates/cmem-eval-longmemeval/src/loader.rs +++ b/crates/cmem-eval-longmemeval/src/loader.rs @@ -213,12 +213,20 @@ fn parse_turns( if !turn.is_object() { return Err(location.error(format!("{field}[{idx}]"), "expected a turn object")); } + let text = string_field(turn, "content").ok_or_else(|| { + location.error(format!("{field}[{idx}].content"), "expected a string") + })?; + let observation_parts = split_observation(&text).ok_or_else(|| { + location.error( + format!("{field}[{idx}].content"), + "a span without a usable boundary exceeds the 8192-token observation limit", + ) + })?; Ok(LongMemEvalTurn { index: idx + 1, speaker: string_field(turn, "role"), - text: string_field(turn, "content").ok_or_else(|| { - location.error(format!("{field}[{idx}].content"), "expected a string") - })?, + text, + observation_parts, has_answer: turn .get("has_answer") .and_then(Value::as_bool) @@ -228,6 +236,77 @@ fn parse_turns( .collect() } +pub(crate) const OBSERVATION_TOKEN_LIMIT: usize = 8192; + +fn observation_tokens(text: &str) -> usize { + // Include the native surface prefix in the provider's token limit. + let clean = text.split_whitespace().collect::>().join(" "); + let surface = if clean.is_empty() { + "Observation excerpt".to_string() + } else { + format!("Observation excerpt: {clean}") + }; + tiktoken_rs::cl100k_base_singleton() + .encode_ordinary(&surface) + .len() +} + +fn split_observation(text: &str) -> Option> { + use cmem_eval::ObservationBoundary::{Newline, Paragraph, Sentence, Whitespace}; + if text.len() + "Observation excerpt: ".len() <= OBSERVATION_TOKEN_LIMIT + || observation_tokens(text) <= OBSERVATION_TOKEN_LIMIT + { + return Some(Vec::new()); + } + let ends = text + .char_indices() + .filter_map(|(index, ch)| { + let end = index + ch.len_utf8(); + let sentence = matches!(ch, '.' | '!' | '?') + && text[end..].chars().next().is_none_or(char::is_whitespace); + let kind = if ch == '\n' + && (text[end..].starts_with('\n') || text[end..].starts_with("\r\n")) + { + Paragraph + } else if sentence { + Sentence + } else if ch == '\n' { + Newline + } else if ch.is_whitespace() { + Whitespace + } else { + return None; + }; + Some((end, kind)) + }) + .collect::>(); + let mut parts = Vec::new(); + let mut start = 0; + while observation_tokens(&text[start..]) > OBSERVATION_TOKEN_LIMIT { + let last_fit = |kinds: &[cmem_eval::ObservationBoundary]| { + ends.iter().rev().copied().find(|(end, kind)| { + *end > start + && kinds.contains(kind) + && observation_tokens(&text[start..*end]) <= OBSERVATION_TOKEN_LIMIT + }) + }; + let (end, boundary_kind) = last_fit(&[Paragraph, Sentence]) + .or_else(|| last_fit(&[Newline])) + .or_else(|| last_fit(&[Whitespace]))?; + parts.push(crate::LongMemEvalObservationPart { + text: text[start..end].to_string(), + boundary_kind, + }); + start = end; + } + // End of the source turn closes its final paragraph without another cut. + parts.push(crate::LongMemEvalObservationPart { + text: text[start..].to_string(), + boundary_kind: Paragraph, + }); + Some(parts) +} + fn string_field(value: &Value, key: &str) -> Option { value .get(key) @@ -252,6 +331,59 @@ fn string_array(value: Option<&Value>) -> Vec { mod tests { use super::*; + #[test] + fn records_preferred_boundary_then_newline_and_whitespace_fallbacks() { + use cmem_eval::ObservationBoundary::{Newline, Paragraph, Sentence, Whitespace}; + for (separator, expected) in [ + (". ", Sentence), + ("\n\n", Paragraph), + ("\n", Newline), + (" ", Whitespace), + ] { + let text = format!( + "{}{separator}{}", + "alpha ".repeat(4500), + "beta ".repeat(4500) + ); + let parts = split_observation(&text).unwrap(); + assert_eq!(parts[0].boundary_kind, expected); + assert_eq!(parts.last().unwrap().boundary_kind, Paragraph); + assert_eq!( + parts + .iter() + .map(|part| part.text.as_str()) + .collect::(), + text + ); + assert!( + parts + .iter() + .all(|part| observation_tokens(&part.text) <= OBSERVATION_TOKEN_LIMIT) + ); + } + } + + #[test] + fn paragraph_split_preserves_unicode_and_crlf_bytes_under_native_token_limit() { + let text = format!("{}\r\n\r\n{}", "猫 ".repeat(2200), "猫 ".repeat(2200)); + assert!(observation_tokens(&text) > OBSERVATION_TOKEN_LIMIT); + let parts = split_observation(&text).unwrap(); + assert_eq!(parts.len(), 2); + assert_eq!( + parts + .iter() + .map(|part| part.text.as_str()) + .collect::(), + text + ); + assert!( + parts + .iter() + .all(|part| observation_tokens(&part.text) <= OBSERVATION_TOKEN_LIMIT) + ); + assert!(split_observation(&" \n".repeat(9000)).unwrap().is_empty()); + } + #[test] fn parses_official_fixture() { let value = serde_json::json!([{ diff --git a/crates/cmem-eval-longmemeval/src/types.rs b/crates/cmem-eval-longmemeval/src/types.rs index fcecbcd8..19c16409 100644 --- a/crates/cmem-eval-longmemeval/src/types.rs +++ b/crates/cmem-eval-longmemeval/src/types.rs @@ -28,6 +28,49 @@ impl LongMemEvalInstance { }) .collect() } + + pub fn observation_splits(&self) -> Vec { + if !self + .sessions + .iter() + .flat_map(|session| &session.turns) + .any(|turn| !turn.observation_parts.is_empty()) + { + return Vec::new(); + } + let identities = crate::identity::Identities::new(self); + self.sessions + .iter() + .zip(&identities.episodes) + .flat_map(|(session, episode)| { + session + .turns + .iter() + .filter(|turn| !turn.observation_parts.is_empty()) + .map(|turn| cmem_eval::ObservationSplit { + dataset_item_id: self.question_id.clone(), + source_observation_id: crate::identity::observation_id( + episode, turn.index, 0, + ), + parts: turn + .observation_parts + .iter() + .enumerate() + .map(|(index, part)| cmem_eval::ObservationSplitPart { + observation_id: crate::identity::observation_id( + episode, turn.index, index, + ), + boundary_kind: part.boundary_kind, + }) + .collect(), + boundary_policy: "greedy_sentence_paragraph_then_newline_whitespace_v1" + .into(), + tokenizer: "cl100k_base".into(), + max_input_tokens: crate::loader::OBSERVATION_TOKEN_LIMIT, + }) + }) + .collect() + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -43,9 +86,26 @@ pub struct LongMemEvalTurn { pub index: usize, pub speaker: Option, pub text: String, + pub observation_parts: Vec, pub has_answer: bool, } +impl LongMemEvalTurn { + pub fn observation_texts(&self) -> impl Iterator { + self.observation_parts + .is_empty() + .then_some(self.text.as_str()) + .into_iter() + .chain(self.observation_parts.iter().map(|part| part.text.as_str())) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LongMemEvalObservationPart { + pub text: String, + pub boundary_kind: cmem_eval::ObservationBoundary, +} + #[derive(Debug, Clone)] pub struct LongMemEvalMemoryInputs { pub episodes: Vec, diff --git a/crates/cmem-eval-longmemeval/tests/observation_splits.rs b/crates/cmem-eval-longmemeval/tests/observation_splits.rs new file mode 100644 index 00000000..c6b68676 --- /dev/null +++ b/crates/cmem-eval-longmemeval/tests/observation_splits.rs @@ -0,0 +1,109 @@ +use cmem_eval::{ObjectType, RetrievedItem}; +use cmem_eval_longmemeval::{ + full_history_text, ingest::to_memory_inputs, load_value, scoring::score, +}; + +#[test] +fn splits_oversized_turn_into_consecutive_observations_without_changing_source_or_gold() { + let text = format!( + "{}End.\n\n{}End.", + "alpha ".repeat(4500), + "beta ".repeat(4500) + ); + let input = serde_json::json!([{ + "question_id": "oversized-turn", + "question": "What happened?", + "haystack_session_ids": ["s1"], + "haystack_dates": ["2023/05/30 (Tue) 23:40"], + "haystack_sessions": [[ + {"role": "user", "content": text, "has_answer": true}, + {"role": "assistant", "content": "Next turn."} + ]], + "answer_session_ids": ["s1"] + }]); + let item = load_value(input.clone()).unwrap().remove(0); + let mapped = to_memory_inputs(&item); + assert_eq!(mapped.observations.len(), 3); + assert_eq!(mapped.observations[0].external_id, "s1:turn:1"); + assert_eq!(mapped.observations[1].external_id, "s1:turn:1:part:2"); + assert_eq!(mapped.observations[2].external_id, "s1:turn:2"); + assert_eq!( + mapped.observations[..2] + .iter() + .map(|o| o.text.as_str()) + .collect::(), + text + ); + for observation in &mapped.observations[..2] { + let surface = format!( + "Observation excerpt: {}", + observation + .text + .split_whitespace() + .collect::>() + .join(" ") + ); + assert!( + tiktoken_rs::cl100k_base_singleton() + .encode_ordinary(&surface) + .len() + <= 8192 + ); + assert_eq!(observation.episode_external_id, "s1"); + assert_eq!(observation.speaker.as_deref(), Some("user")); + assert_eq!( + observation.observed_at.as_deref(), + Some("2023-05-30T23:40:00Z") + ); + assert!(!observation.metadata.to_string().contains("has_answer")); + } + assert_eq!( + full_history_text(&item), + format!("user: {text}\nassistant: Next turn.") + ); + assert_eq!(item.gold_turn_ids(), ["s1:turn:1"]); + let retrieved = mapped.observations[..2] + .iter() + .enumerate() + .map(|(index, observation)| RetrievedItem { + kind: ObjectType::Observation, + internal_id: format!("id-{index}"), + external_id: Some(observation.external_id.clone()), + episode_external_id: Some("s1".into()), + score: None, + rank: index + 1, + rationale: vec![], + text: None, + }) + .collect::>(); + let metrics = score(&item, &retrieved[1..], &[1], &[1]); + assert_eq!(metrics["turn_recall_fraction@1"], 1.0); + assert_eq!(metrics["turn_mrr@1"], 1.0); + assert_eq!( + score(&item, &retrieved, &[1], &[2])["turn_recall_fraction@2"], + 1.0 + ); + assert_eq!( + serde_json::to_value(&item).unwrap(), + serde_json::to_value(load_value(input).unwrap().remove(0)).unwrap() + ); +} + +#[test] +fn unsplittable_oversized_turn_names_the_question_before_ingest() { + let error = load_value(serde_json::json!([{ + "question_id": "unsplittable-turn", + "question": "What happened?", + "haystack_session_ids": ["s1"], + "haystack_sessions": [[{"content": "x".repeat(80000)}]] + }])) + .unwrap_err(); + assert!(matches!( + error, + cmem_eval_longmemeval::LoadError::Admission { .. } + )); + let message = error.to_string(); + assert!(message.contains("unsplittable-turn")); + assert!(message.contains("haystack_sessions[0].turns[0].content")); + assert!(message.contains("8192-token")); +} diff --git a/crates/cmem-eval-runner/src/pipeline.rs b/crates/cmem-eval-runner/src/pipeline.rs index 069e4194..411b72a4 100644 --- a/crates/cmem-eval-runner/src/pipeline.rs +++ b/crates/cmem-eval-runner/src/pipeline.rs @@ -228,6 +228,9 @@ trait DatasetSpec { config: &BenchmarkRunConfig, ) -> Value; fn full_history_text(item: &Self::Item) -> String; + fn observation_splits(_item: &Self::Item) -> Vec { + Vec::new() + } fn enrichment( item: &Self::Item, namespace: &str, @@ -280,6 +283,10 @@ async fn run_pipeline(args: RunArgs) -> Result<()> { header .embedding_bindings .insert(config.dataset.to_string(), embedding_binding); + header.observation_splits = source_items + .iter() + .flat_map(S::observation_splits) + .collect(); adapter = if lexical { None } else { @@ -767,6 +774,10 @@ impl DatasetSpec for LongMemEvalSpec { cmem_eval_longmemeval::full_history_text(item) } + fn observation_splits(item: &Self::Item) -> Vec { + item.observation_splits() + } + fn enrichment( item: &Self::Item, namespace: &str, @@ -1105,6 +1116,7 @@ fn run_header( run_id: config.run_id.clone(), dataset: config.dataset.clone(), input_sha256, + observation_splits: Vec::new(), embedding_bindings: BTreeMap::new(), self_notion_ids: BTreeMap::new(), harness_commit: commit(&workspace)?, diff --git a/crates/cmem-eval-runner/tests/longmemeval_observation_splits.rs b/crates/cmem-eval-runner/tests/longmemeval_observation_splits.rs new file mode 100644 index 00000000..8d181f4b --- /dev/null +++ b/crates/cmem-eval-runner/tests/longmemeval_observation_splits.rs @@ -0,0 +1,93 @@ +use serde_json::json; +use std::{fs, path::PathBuf, process::Command}; + +#[test] +fn run_manifest_records_oversized_turn_split_identities_and_policy() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../.agent-work/evals-worker2"); + fs::create_dir_all(&root).unwrap(); + let directory = tempfile::tempdir_in(root).unwrap(); + let dataset = directory.path().join("fixture.json"); + let config = directory.path().join("config.toml"); + let output = directory.path().join("run/results.jsonl"); + let text = format!( + "{}End.\n\n{}End.", + "alpha ".repeat(4500), + "beta ".repeat(4500) + ); + fs::write( + &dataset, + serde_json::to_vec(&json!([{ + "question_id": "manifest-split", + "question": "What happened?", + "question_date": "2023/05/30 (Tue) 23:40", + "haystack_session_ids": ["s1"], + "haystack_dates": ["2023/05/30 (Tue) 23:40"], + "haystack_sessions": [[{"role": "user", "content": text, "has_answer": true}]], + "answer_session_ids": ["s1"] + }])) + .unwrap(), + ) + .unwrap(); + let mut run_config: cmem_eval::BenchmarkRunConfig = toml::from_str(include_str!( + "../../../configs/longmemeval_s_retrieval.toml" + )) + .unwrap(); + run_config.backend.embedding.provider = cmem_eval::EmbeddingProviderConfig::Deterministic; + run_config.ingest.enrichment_snapshot_path = None; + fs::write(&config, toml::to_string(&run_config).unwrap()).unwrap(); + let result = Command::new(env!("CARGO_BIN_EXE_cmem-eval")) + .env_remove("OPENAI_API_KEY") + .args(["run", "longmemeval-s", "--dataset"]) + .arg(dataset) + .arg("--config") + .arg(config) + .arg("--out") + .arg(&output) + .output() + .unwrap(); + assert!( + result.status.success(), + "{}", + String::from_utf8_lossy(&result.stderr) + ); + let header: cmem_eval::RunHeader = + serde_json::from_slice(&fs::read(output.with_file_name("header.json")).unwrap()).unwrap(); + assert_eq!(header.observation_splits.len(), 1); + let split = &header.observation_splits[0]; + assert_eq!(split.dataset_item_id, "manifest-split"); + assert_eq!(split.source_observation_id, "s1:turn:1"); + assert_eq!( + split + .parts + .iter() + .map(|part| part.observation_id.as_str()) + .collect::>(), + ["s1:turn:1", "s1:turn:1:part:2"] + ); + assert!( + split + .parts + .iter() + .all(|part| part.boundary_kind == cmem_eval::ObservationBoundary::Paragraph) + ); + assert_eq!(split.tokenizer, "cl100k_base"); + assert_eq!(split.max_input_tokens, 8192); + assert_eq!( + split.boundary_policy, + "greedy_sentence_paragraph_then_newline_whitespace_v1" + ); + let rows = cmem_eval::read_jsonl(&output).unwrap(); + assert_eq!(rows[0].question_id, "manifest-split"); + assert_eq!(rows[0].gold_observation_ids, ["s1:turn:1"]); + assert!( + rows[0] + .retrieved + .iter() + .any(|item| item.external_id.as_deref() == Some("s1:turn:1:part:2")) + ); + assert_eq!( + serde_json::to_value(&rows[0].metrics).unwrap()["turn_recall_fraction@5"], + 1.0 + ); + assert!(!output.parent().unwrap().join("stores").exists()); +} diff --git a/crates/cmem-eval/src/results.rs b/crates/cmem-eval/src/results.rs index 9778dae3..7eb7a85d 100644 --- a/crates/cmem-eval/src/results.rs +++ b/crates/cmem-eval/src/results.rs @@ -47,6 +47,7 @@ pub struct RunHeader { pub run_id: String, pub dataset: String, pub input_sha256: String, + pub observation_splits: Vec, /// Scenario id (continuity) or dataset id maps to the embedding used at runtime. pub embedding_bindings: BTreeMap, /// Executed native namespace maps to the self notion passed to its stores. @@ -64,6 +65,31 @@ pub struct RunHeader { pub retain_reason: Option, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ObservationSplit { + pub dataset_item_id: String, + pub source_observation_id: String, + pub parts: Vec, + pub boundary_policy: String, + pub tokenizer: String, + pub max_input_tokens: usize, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ObservationSplitPart { + pub observation_id: String, + pub boundary_kind: ObservationBoundary, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ObservationBoundary { + Paragraph, + Sentence, + Newline, + Whitespace, +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct LatencySummary { pub latency_ms: NumericMetricAggregate, @@ -221,6 +247,7 @@ mod tests { run_id: "r".into(), dataset: "locomo".into(), input_sha256: crate::text_sha256("input"), + observation_splits: Vec::new(), embedding_bindings: BTreeMap::new(), self_notion_ids: BTreeMap::new(), harness_commit: "test".into(), From d2d47d3bff156071871a25cb0d68dfd06de176a6 Mon Sep 17 00:00:00 2001 From: ebigunso Date: Fri, 16 Oct 2026 05:21:52 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9D=20The=20situated=20plan=20meas?= =?UTF-8?q?ures=20the=20library's=20recall=20design=20as=20a=20situation?= =?UTF-8?q?=20view;=20the=20floor=20calibration=20is=20gone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../v0-2-situated-recall-scenarios-plan.md | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md index 29691d00..00d3b5a2 100644 --- a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md +++ b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md @@ -7,14 +7,15 @@ ## Goal - Before the library implements situated recall, this repository can state each v0.2 catalog situation as a scenario a person can read and judge, with assertions that must hold and measures that are reported, and says of each scenario whether it passed, failed, or was not run because the pinned library cannot be asked yet. As the library's v0.2 slices land, the same scenarios start running with no re-authoring, and the pollution and context-size baselines are re-measured once. -- Decision each part informs: the assertions inform the library's acceptance of v0.2 (draft section 6) and the shape of its scene input; recall by reason under the loud-topic set informs route-floor calibration; bystander share and context tokens inform the cost of situated recall; the re-baseline informs whether pack-admission changes cost recall or context. +- Decision each part informs: the assertions inform the library's acceptance of v0.2 (draft section 6) and the shape of its scene input; recall by reason under the loud-topic set and the situation view inform the library's recall design (its brief's section 4a measurement: a deterministic situation view against the behaviour before it); bystander share and context tokens inform the cost of situated recall; the re-baseline informs whether the recall design's change cost recall or context. ## Definition of Done - Every scenario group in the library's v0.2 draft, section 4, exists as at least one scenario that names its catalog situation and states its retrieval-tier property as assertions on its probes and writes (there is no separate property field; the assertions are the property), checked with no language model. - A run reports each scenario as passed, failed, or not run with the missing features named, and reports recall of what the moment calls for grouped by reason, bystander share, and context tokens. A first run against library `d0fe82d` is recorded in the Progress Log as the starting point. - After the library's schema groundwork the workspace builds, the three validation commands pass, and the smoke recipe runs, with no register-cited byte changed. - After the library's scene and routes land, no scenario is "not run", and the draft's section 6 retrieval-tier criteria can be read off one run report. -- The continuity baselines of ADR-I-0022 (pollution, context size, gap recall, the graph-only probe) are re-measured once at the library commit that closes v0.2 pack admission, with the comparability conditions from the census stated beside the numbers. +- The continuity baselines of ADR-I-0022 (pollution, context size, gap recall, the graph-only probe) are re-measured once at the library commit that closes v0.2 recall (the situated recall plan's final tip), with the comparability conditions from the census stated beside the numbers. +- The situation view: the library's section 4a measurement can be read off one deterministic run at a library tip against the before arm captured at `af605e09`, on the pressure families with topic-only and each-cue-removed controls, in both identifier orders, with candidate recall, situation grouping, coverage, unrelated admissions and rendered behaviour reported separately, and the cost at a years-sized synthetic store with skewed hubs. - README describes what ships; the plan closes in completed. - Before closeout, every measurement reading this plan cites is kept at a tracked or recoverable location under the repository's evidence rule, including the census report (`.agent-work/orchestrator/v0-2-eval-census-report.md`) this plan rests on; a path under `.agent-work` is not a location. @@ -291,7 +292,7 @@ Reviewer evidence, for every task below: besides the diff review, the Reviewer p - crates/cmem-eval-continuity/README.md (the scenario contract, reconciled to the same rulings) - depends_on: [Task_4, Task_7, Task_8] - description: | - External dependency: the library slices that add the scene, the reference time, the routes and the trace facts. Each step records the exact library commit it validates against in the Decision Log and the run headers, with the sibling checkout pinned to it. Extend together, as each slice lands, the core adapter contract and its forwarding, the driver's one mapping function, and the supported feature set beside it; the reference time is forwarded (today `query_date` stops at the adapter). May land in steps; each step reduces the "not run" count and never re-authors a scenario to fit the library. + External dependency: the library slices that add the scene, the reference time, the routes and the trace facts, and, from 2026-10-16, the library's situated recall plan (its brief's section 4a), whose contract replaces the pack, its sections and the per-cue floors with situations under one bound. Each step records the exact library commit it validates against in the Decision Log and the run headers, with the sibling checkout pinned to it. Extend together, as each slice lands, the core adapter contract and its forwarding, the driver's one mapping function, and the supported feature set beside it; the reference time is forwarded (today `query_date` stops at the adapter). The step that follows the library's Task_2 (the contract) moves the adapter and the driver off `pack`, `section_limits` and `cue_floors` onto situations, parts, paths, ingredients, cuts and absences, and forwards the one bound; the direction, due state and due cue step (390e8694, held) is folded into it. May land in steps; each step reduces the "not run" count and never re-authors a scenario to fit the library. - acceptance: - A feature joins the supported set only together with a drift test showing a distinctive authored value reaches the library-facing input whole, so a dropped or coerced field fails in the harness and is never blamed on the library. - No scenario in the situated fixtures is "not run". @@ -307,36 +308,36 @@ Reviewer evidence, for every task below: besides the diff review, the Reviewer p owner: reviewer detail: "Tier D diff review with the reviewer evidence clause; Tier A check that no scenario was weakened to pass" -### Task_9: The library's provisional cue floors are calibrated for its cues slice +### Task_9: The library's recall design is measured as a situation view against the behaviour before it - type: test - owns: - - crates/cmem-eval-continuity/fixtures/** (new calibration fixture files only; no existing fixture is edited) - - crates/cmem-eval-continuity/src/** + - crates/cmem-eval-continuity/fixtures/** (new fixture files only; no existing fixture is edited) + - crates/cmem-eval-continuity/src/** (the situation-view reader, the added controls, the synthetic years-sized generator; the floor sweep in `calibrate_cue_floors` is removed once the library has no floors) - crates/cmem-eval/src/memory_adapter.rs - crates/cmem-eval/src/adapter.rs - crates/cmem-eval-continuity/README.md - README.md - depends_on: [Task_5] - description: | - Dependency note: this task needs only Task_5's cue-forwarding step (the library's cues slice), not all of Task_5; it runs as soon as that step lands, before the remaining routes, so provisional cue floors are calibrated in time for the cues slice. - External dependency: the library's cues slice at a pinned commit (the activity beside the topic, cue kinds in the trace, floors per cue kind at `context.cue_floors`, provisional at 1 each). Task_5 step 2 forwards those; this task measures. A generated calibration fixture, separate from the situated scenarios and never asserted as pass or fail, puts pressure on each kind whose floor the library claims (participant, place, activity) under a loud topic and against each other, at the library's default expansion depth, and sweeps the floor values. It reports, per kind and per floor value: whether a memory reachable only through that kind is admitted (starvation), and what each floor admission displaced and at what score, including the case where the scene's words match nothing the character has lived (pollution: a floor admits the least-bad neighbour). It reads cue kinds and floor admissions from the library's trace, and derives whether a kind was found directly or inherited through expansion from the candidate, root and relation traces; if that cannot be derived, it says so and reports to the library plan's owner. Lab-notebook grade: numbers, config and input hashes and the library commit; nothing sealed; every store it creates is cleaned up. It does not replace Task_6. + External dependency: the library's situated recall plan at a pinned commit (its Task_2 contract for the reader; its Task_4 tip and final tip for the after arms). The before arm is already captured at library `af605e09` with the existing instrument (`.agent-work/worker/situated-recall-before-arm.md`, controls gap note beside it). This task builds the instrument the library's section 4a measurement names and runs it: a reader over the new result that reports, per probe, the situations with their anchors, parts, paths, reported ingredients, cuts and absence states; the pressure families (overlapping office and topic, two people with separate obligations, duplicated accounts, old high-salience matters, keyless scenes, empty queries) with topic-only and each-cue-removed controls, adding the isolated controls the gap note lists as missing (Rowan-only, two-person no-topic, per-description removal, and the rest); both identifier orders where the binary supports them and an opposed-identifier mode for the authored narratives; hand-labelled situations for two families, authored in the fixture as gold that never reaches the library; five measures reported separately (candidate recall against the before arm, situation grouping against the labels, coverage, unrelated admissions, rendered behaviour from the memo); and a deterministic synthetic years-sized store with skewed hubs for the cost measure, timed outside the deterministic output. Floor 0 is never read as cue removal. Lab-notebook grade: numbers, config and input hashes and the library commit; nothing sealed; every store it creates is cleaned up. It does not replace Task_6. - acceptance: - - For each of the three kinds, the report gives starvation and pollution at each swept floor value, with an executed and not-run census, at the pinned library commit recorded in the header. - - The report states how much floor credit came from an inherited kind, or that the trace cannot tell. - - Deterministic: the run repeats with zero differences. No gold reaches library input. + - At the library's Task_4 tip and at its final tip, one run reports the five measures per pressure family against the before arm, with the controls, both identifier orders, and the executed and not-run census, the library commit in the header. + - The grouping measure is scored against hand-labelled situations in two families, and the labels are in the fixture, not derived from any run. + - The cost measure reports retrieval time against store size at the synthetic years-sized store, with the hub skew stated. + - Deterministic: each run repeats with zero differences (timing outside the deterministic output). No gold reaches library input. - validation: - kind: command required: true owner: worker - detail: "the three repository validation commands; the calibration run twice and the repeat comparison; sha256 of the protected assets" + detail: "the three repository validation commands; the situation-view run twice and the repeat comparison at each library tip; sha256 of the protected assets" - kind: review required: true owner: reviewer - detail: "Tier D diff review with the reviewer evidence clause" + detail: "Tier D diff review with the reviewer evidence clause; Tier A check that the labels and controls test the library's design and not its implementation, and that no family was weakened" - kind: review required: true owner: orchestrator - detail: "The numbers are handed to the library plan, whose owner sets the measured floors" + detail: "The numbers are handed to the library's situated recall plan, whose Decision Log carries the verdict" ### Task_6: The continuity baselines are re-measured once - type: test @@ -345,9 +346,9 @@ Reviewer evidence, for every task below: besides the diff review, the Reviewer p - docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md - depends_on: [Task_5, Task_9] - description: | - At the library commit that closes v0.2 pack admission: recall by reason and cost over the loud-topic set, and one re-measurement of pollution, context size, gap recall and the graph-only probe on the canonical 15-scenario set, under the comparability conditions in the census (question 4). The canonical set reuses its existing frozen store; existing stores are never touched. Lab-notebook grade: numbers, config and input hashes, and both commits in the Decision Log; nothing sealed. + At the library commit that closes v0.2 recall (the situated recall plan's final tip): recall by reason and cost over the loud-topic set, and one re-measurement of pollution, context size, gap recall and the graph-only probe on the canonical 15-scenario set, under the comparability conditions in the census (question 4). The canonical set reuses its existing frozen store; existing stores are never touched. Lab-notebook grade: numbers, config and input hashes, and both commits in the Decision Log; nothing sealed. - acceptance: - - Before and after numbers sit side by side with every intentional difference (library commit, ingest shape, fixture file if regenerated, floors) named. + - Before and after numbers sit side by side with every intentional difference (library commit, ingest shape, fixture file if regenerated, the recall design's contract) named. - Deterministic: the run repeats with zero differences. - All run stores cleaned up. - validation: @@ -368,7 +369,7 @@ Reviewer evidence, for every task below: besides the diff review, the Reviewer p - Wave 2c: [Task_8] (needs nothing from the library; stacks on Wave 2b) - Wave 3: [Task_4] (starts when the library groundwork branch exists) - Wave 4: [Task_5] (follows library slices; may be several steps) -- Wave 4b: [Task_9] (after the Task_5 step that forwards the library's cues slice) +- Wave 4b: [Task_9] (after the Task_5 step that moves the adapter onto situations; runs at the library's Task_4 tip and final tip) - Wave 5: [Task_6] Every task is one pull request, and the pull requests form one linear stack for review. The stack does not land level by level: the lessons file requires every level that reaches main to pass against the library revision CI resolves, and the levels below Task_4 cannot, since they were written against the library before its schema groundwork. When the decider approves the merge, the top branch lands on main as one pull request, validated at the library main revision CI resolves and with that revision recorded, and the per-task pull requests are closed as reviewed and superseded by it. Main therefore never holds a level that fails to build. Tasks a wave runs in parallel are each developed from the wave's integrated parent and then stacked in task order (Task_2's pull request, then Task_3's on top of it), the later one merging the earlier forward. CI resolves library main, which carries the schema groundwork since 2026-09-21, so every pull request below Task_4 is red by construction and only the top of the stack is expected green. Later waves stack on top as their library dependency exists, and pin the exact library commit they validate against. @@ -481,3 +482,8 @@ Every task is one pull request, and the pull requests form one linear stack for - Carried to Task_6: eighteen rationale-category metric keys and `hub_expansion_relevant_hit_rate` are retired; sealed outputs keep their old names. - Carried to Task_9: calibrate at a library commit that honors numeric floors at root selection and shares only the room beyond them. - User approval: inside the approved plan. Record proposed: none. +- 2026-10-16 Decision: the plan is amended for the library's design amendment (the v0.2 completion brief's section 4a, ratified 2026-10-16): the library replaces the pack, its sections and the per-cue floors with situations under one bound, so there is no floor to calibrate. + - Trigger / new insight: the owner's re-examination of the v0.2 recall design; the library's situated recall plan (`docs/coding-agent/plans/active/v0-2-situated-recall-plan.md` there) names the measurement this repository provides: a deterministic situation view against the behaviour before it, on the existing pressure cases, with topic-only and each-cue-removed controls, five measures reported separately, and cost at a years-sized store. + - Plan delta: Task_9 (cue-floor calibration) is replaced by the situation-view instrument; Task_6 is re-anchored to the library commit that closes v0.2 recall; Task_5 gains the step that moves the adapter off `pack` and forwards the one bound, and absorbs the held direction and due step (390e8694); the Goal and Definition of Done say what the measurements now inform. The before arm was captured on 2026-10-16 at library `af605e09` with the existing instrument (`.agent-work/worker/situated-recall-before-arm.md`; the controls gap note beside it is Task_9's input). The situated fixtures stand unchanged. + - User approval: the owner's ratified amendment sets the direction; this revision of an approved plan is reviewed before its tasks run. + - Record proposed: none. From e8e805d30a39d2dc38ab7c71d8f22e17d95d4a1b Mon Sep 17 00:00:00 2001 From: ebigunso Date: Fri, 16 Oct 2026 05:28:03 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=93=9D=20Situated=20plan:=20the=20bef?= =?UTF-8?q?ore=20arm=20gains=20its=20controls=20and=20cost=20store=20now;?= =?UTF-8?q?=20the=20reader=20runs=20at=20three=20library=20tips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../v0-2-situated-recall-scenarios-plan.md | 54 +++++++++++++------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md index 00d3b5a2..33c26845 100644 --- a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md +++ b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md @@ -15,7 +15,7 @@ - After the library's schema groundwork the workspace builds, the three validation commands pass, and the smoke recipe runs, with no register-cited byte changed. - After the library's scene and routes land, no scenario is "not run", and the draft's section 6 retrieval-tier criteria can be read off one run report. - The continuity baselines of ADR-I-0022 (pollution, context size, gap recall, the graph-only probe) are re-measured once at the library commit that closes v0.2 recall (the situated recall plan's final tip), with the comparability conditions from the census stated beside the numbers. -- The situation view: the library's section 4a measurement can be read off one deterministic run at a library tip against the before arm captured at `af605e09`, on the pressure families with topic-only and each-cue-removed controls, in both identifier orders, with candidate recall, situation grouping, coverage, unrelated admissions and rendered behaviour reported separately, and the cost at a years-sized synthetic store with skewed hubs. +- The situation view: the library's section 4a measurement can be read off one deterministic run at a library tip against the before arm captured at `af605e09` (the existing families on 2026-10-16, the added controls and the cost store by Task_10 at the same pin), on the pressure families with topic-only and each-cue-removed controls, in both identifier orders, with candidate recall, situation grouping, coverage, unrelated admissions and rendered behaviour reported separately, and the cost at a years-sized synthetic store with skewed hubs. - README describes what ships; the plan closes in completed. - Before closeout, every measurement reading this plan cites is kept at a tracked or recoverable location under the repository's evidence rule, including the census report (`.agent-work/orchestrator/v0-2-eval-census-report.md`) this plan rests on; a path under `.agent-work` is not a location. @@ -73,7 +73,7 @@ SHA-256, all under `crates/cmem-eval-continuity/fixtures/`: - `embeddings/continuity_benchmarks_v1_manifest.json` `a63b35f0ba2ef4deac06dcf8805822765a0bb5fdced8f43f91d0570a6d151714` - `embeddings/continuity_benchmarks_v1_store.json` `c1f1eeaa45c1872e2284ec069fbd205ed3c1b1656ee89007accb3dd44657352b` -Comparability conditions for Task_6 (from the census, question 4): the same scenario subset and labels; the same provider, width and text hashes; the same section, root and fanout settings and the scored-versus-fallback regime, with floor settings reported beside them; unchanged recall and pollution denominators and sampled-negative semantics; the recurrence, temporal-contrast, thread and episode preservation checks kept; deterministic repeats compared by identity, rank and metric with timing recorded apart; every intentional difference (library commit, ingest shape, fixture file, floors) named beside the numbers. The 15-scenario canonical comparison stays distinct from ADR-I-0022's ten-scenario aggregate. +Comparability conditions for Task_6 (from the census, question 4): the same scenario subset and labels; the same provider, width and text hashes; the same section, root and fanout settings and the scored-versus-fallback regime, with the recall design's contract (sections and floors before, situations under one bound after) named as the intentional difference; unchanged recall and pollution denominators and sampled-negative semantics; the recurrence, temporal-contrast, thread and episode preservation checks kept; deterministic repeats compared by identity, rank and metric with timing recorded apart; every intentional difference (library commit, ingest shape, fixture file, the recall design's contract) named beside the numbers. The 15-scenario canonical comparison stays distinct from ADR-I-0022's ten-scenario aggregate. ## Open Questions (max 3) - Q1: If assumption A1 fails (the move to notions changes the embedded text of the canonical set), may a new store be frozen for it with one live provider call (new file, new hash; existing stores untouched), and is the before and after then accepted as a comparison with changed content? Recommended: yes to both, decided only if Task_4 reports the failure. No live call is planned otherwise. @@ -311,33 +311,55 @@ Reviewer evidence, for every task below: besides the diff review, the Reviewer p ### Task_9: The library's recall design is measured as a situation view against the behaviour before it - type: test - owns: - - crates/cmem-eval-continuity/fixtures/** (new fixture files only; no existing fixture is edited) - - crates/cmem-eval-continuity/src/** (the situation-view reader, the added controls, the synthetic years-sized generator; the floor sweep in `calibrate_cue_floors` is removed once the library has no floors) + - crates/cmem-eval-continuity/src/** (the situation-view reader over the new result; the floor sweep in `calibrate_cue_floors` removed once the library has no floors) - crates/cmem-eval/src/memory_adapter.rs - crates/cmem-eval/src/adapter.rs - crates/cmem-eval-continuity/README.md - README.md -- depends_on: [Task_5] +- depends_on: [Task_5, Task_10] - description: | - External dependency: the library's situated recall plan at a pinned commit (its Task_2 contract for the reader; its Task_4 tip and final tip for the after arms). The before arm is already captured at library `af605e09` with the existing instrument (`.agent-work/worker/situated-recall-before-arm.md`, controls gap note beside it). This task builds the instrument the library's section 4a measurement names and runs it: a reader over the new result that reports, per probe, the situations with their anchors, parts, paths, reported ingredients, cuts and absence states; the pressure families (overlapping office and topic, two people with separate obligations, duplicated accounts, old high-salience matters, keyless scenes, empty queries) with topic-only and each-cue-removed controls, adding the isolated controls the gap note lists as missing (Rowan-only, two-person no-topic, per-description removal, and the rest); both identifier orders where the binary supports them and an opposed-identifier mode for the authored narratives; hand-labelled situations for two families, authored in the fixture as gold that never reaches the library; five measures reported separately (candidate recall against the before arm, situation grouping against the labels, coverage, unrelated admissions, rendered behaviour from the memo); and a deterministic synthetic years-sized store with skewed hubs for the cost measure, timed outside the deterministic output. Floor 0 is never read as cue removal. Lab-notebook grade: numbers, config and input hashes and the library commit; nothing sealed; every store it creates is cleaned up. It does not replace Task_6. + External dependency: the library's situated recall plan at pinned commits (its Task_2 contract tip, its Task_4 tip, its final tip). The before arm is captured at library `af605e09`: the existing families on 2026-10-16 (`.agent-work/worker/situated-recall-before-arm.md`) and the added controls and the cost store by Task_10 at the same pin. This task builds the reader the library's section 4a measurement names and runs it: per probe, the situations with their anchors, parts, paths, reported ingredients, cuts and absence states; the pressure families (overlapping office and topic, two people with separate obligations, duplicated accounts, old high-salience matters, keyless scenes, empty queries) with their topic-only and matched cue-removed controls (Task_10's set); both identifier orders where the calibration binary supports them, the authored narratives in their authored order as captured; the five measures reported separately: candidate recall against the before arm, situation grouping against Task_10's hand-labelled situations, coverage, unrelated admissions, and rendered behaviour from the library's memo at the final tip only, measured and not compared (no before arm renders); the cost measure at Task_10's synthetic store. Runs: at the library's Task_2 tip, candidate recall only with the other measures reported not run (the library's Task_2 control item); at its Task_4 tip, the first four measures; at its final tip, all five. Floor 0 is never read as cue removal. Lab-notebook grade: numbers, config and input hashes and the library commit; nothing sealed; every store it creates is cleaned up. It does not replace Task_6. - acceptance: - - At the library's Task_4 tip and at its final tip, one run reports the five measures per pressure family against the before arm, with the controls, both identifier orders, and the executed and not-run census, the library commit in the header. - - The grouping measure is scored against hand-labelled situations in two families, and the labels are in the fixture, not derived from any run. - - The cost measure reports retrieval time against store size at the synthetic years-sized store, with the hub skew stated. - - Deterministic: each run repeats with zero differences (timing outside the deterministic output). No gold reaches library input. + - At the library's Task_2 tip, one run reports candidate recall per pressure family against the before arm with the other measures not run; at its Task_4 tip, candidate recall, grouping, coverage and unrelated admissions; at its final tip, all five, each with the controls, both identifier orders, the executed and not-run census, and the library commit in the header. + - The grouping measure is scored against Task_10's labels, which are in the fixture and derived from no run. + - The cost measure reports retrieval time against store size at Task_10's synthetic store, hub skew stated, timed outside the deterministic output. + - Deterministic: a run at each tip repeats with zero differences, which shows determinism only; the regression evidence is the comparison against the before arm. No gold reaches library input. - validation: - kind: command required: true owner: worker - detail: "the three repository validation commands; the situation-view run twice and the repeat comparison at each library tip; sha256 of the protected assets" + detail: "the three repository validation commands; the situation-view run at each library tip, repeated once, and the comparison against the before arm; sha256 of the protected assets" - kind: review required: true owner: reviewer - detail: "Tier D diff review with the reviewer evidence clause; Tier A check that the labels and controls test the library's design and not its implementation, and that no family was weakened" + detail: "Tier D diff review with the reviewer evidence clause; Tier A check that the reader tests the library's design and not its implementation, and that no family was weakened" - kind: review required: true owner: orchestrator - detail: "The numbers are handed to the library's situated recall plan, whose Decision Log carries the verdict" + detail: "The numbers are handed to the library's situated recall plan with a reading of what they mean for the character and an explicit verdict per measure; a bare table is not a handoff" + +### Task_10: The before arm gains the matched controls and the years-sized store +- type: test +- owns: + - crates/cmem-eval-continuity/fixtures/** (new fixture files only: the matched cue-removed controls, the hand-labelled situations for two families, the synthetic generator's seed; no existing fixture is edited) + - crates/cmem-eval-continuity/src/bin/calibrate_cue_floors/** (the added control scenarios and the synthetic years-sized generator with skewed hubs, beside the existing families; no existing scenario changed) + - crates/cmem-eval-continuity/README.md +- depends_on: [] +- description: | + Runs now, on the current adapter at library `af605e09` (code equal to b3aba0b2; harness 3a712252 or this branch), before Task_5's step moves the adapter. It adds what the before-arm gap note (`.agent-work/worker/situated-recall-before-arm-controls.md`) lists as missing matched cue-removed controls for the pressure families (Rowan-only and two-person no-topic for the two-obligation cases; per-description removal for the keyless and description cases; the single-cue removals the note names for the remaining families), not route ablations (Due-off, recency-disabled, time-derived routes), which have no referent in the recall design; hand-labelled situations for two families (the overlapping office and the two-obligation cases), written by a person as gold in the fixture; and a deterministic synthetic generator for a years-sized store with skewed hubs (a few people and places with most of the experiences, a long tail with few), with its seed and size in the fixture. It then captures the before arm for the added controls and the cost store at `af605e09`, both identifier orders, twice, and files the manifest beside the existing one. +- acceptance: + - The added controls run under the existing calibration binary at `af605e09` and the manifest lists each with its SHA256, both identifier orders, run twice with zero differences. + - The labels are in the fixture for two families, each situation named with the memories a person would hold together, and never read by the library. + - The synthetic store builds deterministically at the stated size and skew, its retrieval time at `af605e09` is recorded outside the deterministic output, and every store is cleaned up. +- validation: + - kind: command + required: true + owner: worker + detail: "the three repository validation commands; the before-arm runs twice and the repeat comparison; sha256 of the protected assets" + - kind: review + required: true + owner: reviewer + detail: "Tier D diff review with the reviewer evidence clause; the controls are matched removals of one cue each, the labels are gold, the generator is deterministic" ### Task_6: The continuity baselines are re-measured once - type: test @@ -369,7 +391,8 @@ Reviewer evidence, for every task below: besides the diff review, the Reviewer p - Wave 2c: [Task_8] (needs nothing from the library; stacks on Wave 2b) - Wave 3: [Task_4] (starts when the library groundwork branch exists) - Wave 4: [Task_5] (follows library slices; may be several steps) -- Wave 4b: [Task_9] (after the Task_5 step that moves the adapter onto situations; runs at the library's Task_4 tip and final tip) +- Wave 4a: [Task_10] (now, at library af605e09, before Task_5's step moves the adapter) +- Wave 4b: [Task_9] (after Task_10 and the Task_5 step that moves the adapter onto situations; runs at the library's Task_2 tip, Task_4 tip and final tip) - Wave 5: [Task_6] Every task is one pull request, and the pull requests form one linear stack for review. The stack does not land level by level: the lessons file requires every level that reaches main to pass against the library revision CI resolves, and the levels below Task_4 cannot, since they were written against the library before its schema groundwork. When the decider approves the merge, the top branch lands on main as one pull request, validated at the library main revision CI resolves and with that revision recorded, and the per-task pull requests are closed as reviewed and superseded by it. Main therefore never holds a level that fails to build. Tasks a wave runs in parallel are each developed from the wave's integrated parent and then stacked in task order (Task_2's pull request, then Task_3's on top of it), the later one merging the earlier forward. CI resolves library main, which carries the schema groundwork since 2026-09-21, so every pull request below Task_4 is red by construction and only the top of the stack is expected green. Later waves stack on top as their library dependency exists, and pin the exact library commit they validate against. @@ -484,6 +507,7 @@ Every task is one pull request, and the pull requests form one linear stack for - User approval: inside the approved plan. Record proposed: none. - 2026-10-16 Decision: the plan is amended for the library's design amendment (the v0.2 completion brief's section 4a, ratified 2026-10-16): the library replaces the pack, its sections and the per-cue floors with situations under one bound, so there is no floor to calibrate. - Trigger / new insight: the owner's re-examination of the v0.2 recall design; the library's situated recall plan (`docs/coding-agent/plans/active/v0-2-situated-recall-plan.md` there) names the measurement this repository provides: a deterministic situation view against the behaviour before it, on the existing pressure cases, with topic-only and each-cue-removed controls, five measures reported separately, and cost at a years-sized store. - - Plan delta: Task_9 (cue-floor calibration) is replaced by the situation-view instrument; Task_6 is re-anchored to the library commit that closes v0.2 recall; Task_5 gains the step that moves the adapter off `pack` and forwards the one bound, and absorbs the held direction and due step (390e8694); the Goal and Definition of Done say what the measurements now inform. The before arm was captured on 2026-10-16 at library `af605e09` with the existing instrument (`.agent-work/worker/situated-recall-before-arm.md`; the controls gap note beside it is Task_9's input). The situated fixtures stand unchanged. + - Plan delta: Task_9 (cue-floor calibration) is replaced by the situation-view reader and its runs at three library tips; Task_10 captures the added controls, the labels and the years-sized store on the before side now; Task_6 is re-anchored to the library commit that closes v0.2 recall; Task_5 gains the step that moves the adapter off `pack` and forwards the one bound, and absorbs the held direction and due step (390e8694); the Goal and Definition of Done say what the measurements now inform. The before arm was captured on 2026-10-16 at library `af605e09` with the existing instrument (`.agent-work/worker/situated-recall-before-arm.md`; the controls gap note beside it is Task_9's input). The situated fixtures stand unchanged. - User approval: the owner's ratified amendment sets the direction; this revision of an approved plan is reviewed before its tasks run. - Record proposed: none. +- 2026-10-16 Decision: the review of d2d47d3 (CHANGES REQUESTED) applied. The added controls and the cost store get their before arm now (Task_10 at af605e09) rather than after-only; Task_9 runs at the library's Task_2 tip for candidate recall (the library's Task_2 control item), at its Task_4 tip for four measures, and at its final tip for all five, rendered behaviour measured only at the final tip and not compared; "and the rest" bounded to the gap note's matched cue removals, route ablations out; the opposed-identifier mode for authored narratives dropped (identifier-order independence is checked on the calibration families, B2 in authored order as captured); Task_6's comparability paragraph names the contract as the intentional difference; the handoff carries a reading and a verdict per measure; determinism runs show determinism only. From 656c266be5e3b90112502bb593a56279be8d1748 Mon Sep 17 00:00:00 2001 From: ebigunso Date: Fri, 16 Oct 2026 05:31:26 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=93=9D=20Situated=20plan:=20Task=5F10?= =?UTF-8?q?=20owns=20the=20calibration=20binary's=20entry=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../plans/active/v0-2-situated-recall-scenarios-plan.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md index 33c26845..95f73474 100644 --- a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md +++ b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md @@ -343,6 +343,7 @@ Reviewer evidence, for every task below: besides the diff review, the Reviewer p - owns: - crates/cmem-eval-continuity/fixtures/** (new fixture files only: the matched cue-removed controls, the hand-labelled situations for two families, the synthetic generator's seed; no existing fixture is edited) - crates/cmem-eval-continuity/src/bin/calibrate_cue_floors/** (the added control scenarios and the synthetic years-sized generator with skewed hubs, beside the existing families; no existing scenario changed) + - crates/cmem-eval-continuity/src/bin/calibrate_cue_floors.rs (module declarations, family registration, and the controls of the families defined in it; no existing scenario changed) - crates/cmem-eval-continuity/README.md - depends_on: [] - description: | @@ -511,3 +512,4 @@ Every task is one pull request, and the pull requests form one linear stack for - User approval: the owner's ratified amendment sets the direction; this revision of an approved plan is reviewed before its tasks run. - Record proposed: none. - 2026-10-16 Decision: the review of d2d47d3 (CHANGES REQUESTED) applied. The added controls and the cost store get their before arm now (Task_10 at af605e09) rather than after-only; Task_9 runs at the library's Task_2 tip for candidate recall (the library's Task_2 control item), at its Task_4 tip for four measures, and at its final tip for all five, rendered behaviour measured only at the final tip and not compared; "and the rest" bounded to the gap note's matched cue removals, route ablations out; the opposed-identifier mode for authored narratives dropped (identifier-order independence is checked on the calibration families, B2 in authored order as captured); Task_6's comparability paragraph names the contract as the intentional difference; the handoff carries a reading and a verdict per measure; determinism runs show determinism only. +- 2026-10-16 Decision: the re-review of e8e805d applied: Task_10's owns gain the binary's entry file; the controls gap note is Task_10's input, not Task_9's. Task_10 is dispatched (Wave 4a). From 42721dd50fbf6ef78e2052183095e03ef432f74d Mon Sep 17 00:00:00 2001 From: ebigunso Date: Fri, 16 Oct 2026 07:31:24 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=93=9D=20Situated=20plan:=20Task=5F10?= =?UTF-8?q?'s=20cost=20store=20sizes=20and=20incremental=20writes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../plans/active/v0-2-situated-recall-scenarios-plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md index 95f73474..311f6d18 100644 --- a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md +++ b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md @@ -513,3 +513,4 @@ Every task is one pull request, and the pull requests form one linear stack for - Record proposed: none. - 2026-10-16 Decision: the review of d2d47d3 (CHANGES REQUESTED) applied. The added controls and the cost store get their before arm now (Task_10 at af605e09) rather than after-only; Task_9 runs at the library's Task_2 tip for candidate recall (the library's Task_2 control item), at its Task_4 tip for four measures, and at its final tip for all five, rendered behaviour measured only at the final tip and not compared; "and the rest" bounded to the gap note's matched cue removals, route ablations out; the opposed-identifier mode for authored narratives dropped (identifier-order independence is checked on the calibration families, B2 in authored order as captured); Task_6's comparability paragraph names the contract as the intentional difference; the handoff carries a reading and a verdict per measure; determinism runs show determinism only. - 2026-10-16 Decision: the re-review of e8e805d applied: Task_10's owns gain the binary's entry file; the controls gap note is Task_10's input, not Task_9's. Task_10 is dispatched (Wave 4a). +- 2026-10-16 Decision: Task_10 runs the cost store at one and three years (366 and 1096 days), both orders, twice; the six-year size stays in the generator uncaptured (about an hour per order; a daemon restart killed the first capture), and the instrument writes rows incrementally so an interruption keeps finished families. From a1a1688c6af5221619182d3882ea732eeea7f5a1 Mon Sep 17 00:00:00 2001 From: ebigunso Date: Fri, 16 Oct 2026 09:28:42 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=93=9D=20Situated=20plan:=20Task=5F10?= =?UTF-8?q?=20done,=20the=20before=20arm=20complete=20at=20af605e09?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../plans/active/v0-2-situated-recall-scenarios-plan.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md index 311f6d18..01f39b15 100644 --- a/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md +++ b/docs/coding-agent/plans/active/v0-2-situated-recall-scenarios-plan.md @@ -426,6 +426,8 @@ Every task is one pull request, and the pull requests form one linear stack for - Summary: salience on experiences, the `cued` assertion and pooled recall per reason entered the scenario language and the run (PR 58), closing the Wave 2 gap that recent high-salience episodes could not be authored; Task_8 and Task_5 build on it. - Validation evidence: recorded on PR 58; CI there is red only by the library-main construction noted below. +- 2026-10-16: Task_10 done (9270f510 plus the recovery commit ea45f0bd on `feature/2026-10-16/situated-task10`; Tier D APPROVED). The before arm at library af605e09 now holds 142 rows per arm: ninety matched single-cue removals across seven families, the two label families, the one- and three-year synthetic stores, both identifier orders, A and B byte-identical (raw SHA256 2f51b83a...). Report `.agent-work/worker/situated-task10-report.md`; manifest and comparison beside it. Task_9 waits on the library's Task_2 tip. + ## Decision Log (append-only; re-plans and major discoveries) - 2026-09-20 Decision: the scenario shape was redrawn before approval, after the decider asked for the dataset representation to be picked apart.