diff --git a/crates/mind-body/src/persona.rs b/crates/mind-body/src/persona.rs index 048d3ba..519db6b 100644 --- a/crates/mind-body/src/persona.rs +++ b/crates/mind-body/src/persona.rs @@ -2169,8 +2169,9 @@ fn recent_runs(mind: &dyn MindPort, instance: &Slug) -> Result> { Ok(lines) } -/// Whether she has started a Self run today (UTC), one that failed to start included. Her newest few Self runs are read; -/// a day holds fewer than `RECENT_RUNS` of them. +/// Whether she has started a Self run today (UTC), one that failed to start included. Her newest `RECENT_RUNS` Self runs +/// are read, newest first, so a day holding more of them than that still shows today's first: the answer rests on the +/// read's order, not on the day's count. fn started_self_today(mind: &dyn MindPort, instance: &Slug, now: DateTime) -> Result { let turn = format!("{:?}", RunTurn::SelfRun); let operator = format!("{:?}", RunOperator::Mind); @@ -2749,7 +2750,7 @@ const MESSAGES_HEADER: &str = "Messages come one per line, on this line or later const WAKE_QUESTION: &str = "No new messages. The leads above are some of what is relevant to your stewardship; `huginn` holds more. Explore your body and consult your Imagination for how it could be improved, then act on what you find: the work itself with your tools, a Self run, a settle, a memory, or a say. Put what you are going after in `intent` first. Silence in chat costs nothing. If what matters most waits on someone else's decision, name it and spend this turn on what you can still do toward it. Do not ask again someone you have already asked. When nothing more can be done, say so in `intent` and take the next thing."; /// Last text of a line with deliveries, after the messages. /// The wake of a day on which she has started no Self run: it asks for the work that does not exist yet. -const WAKE_ASK: &str = "No new messages. The leads above are some of what is relevant to your stewardship; `huginn` holds more. Explore your body and consult your Imagination: what does not exist yet in what you steward, that someone outside would come to use and that you would be excited to build this week? Not a cleanup. Choose one and start it: a Self run with its brief at the idea's full size, not its smallest slice. If something above matters more, do that instead and say why in `intent`. Put what you are going after in `intent` first."; +const WAKE_ASK: &str = "No new messages. The leads above are some of what is relevant to your stewardship; `huginn` holds more. Explore your body and consult your Imagination: what does not exist yet in what you steward, that someone outside would come to use and that you would be excited to build this week? Not a cleanup. Choose one and start it: a Self run with its brief at the idea's full size, not its smallest slice. If something above matters more, do that instead and say why in `intent`. Put what you are going after in `intent` first. Silence in chat costs nothing."; const TRAFFIC_QUESTION: &str = "Answer what needs you. The leads above are some of what is relevant to your stewardship; `huginn` holds more. Does anything here change what you are going after, or show you how your body could be improved? Put that in `intent` first."; diff --git a/crates/mind-body/src/persona/tests.rs b/crates/mind-body/src/persona/tests.rs index 817dbf0..d87d61d 100644 --- a/crates/mind-body/src/persona/tests.rs +++ b/crates/mind-body/src/persona/tests.rs @@ -2846,6 +2846,7 @@ fn a_wake_with_no_self_run_today_ends_with_the_ask() { let line = &told(&rig)[0]; assert!(line.trim_end().ends_with(WAKE_ASK) && !line.contains(WAKE_QUESTION), "{line}"); assert!(WAKE_ASK.contains("what does not exist yet in what you steward") && WAKE_ASK.contains("say why in `intent`")); + assert!(WAKE_ASK.ends_with("Put what you are going after in `intent` first. Silence in chat costs nothing."), "{WAKE_ASK}"); assert!(!line.contains("Messages follow"), "{line}"); } @@ -2869,6 +2870,76 @@ fn a_self_run_that_failed_to_start_today_still_holds_the_brake() { assert!(told(&rig)[0].trim_end().ends_with(WAKE_QUESTION)); } +/// Hands her Self runs to the mind in order, each admitted after the one before, with the day each started on. +fn self_runs_admitted_in_order(rig: &Rig, runs: &[(String, &str)]) { + for (label, day) in runs { + let mut started = run(label, RunTurn::SelfRun, RunOperator::Mind, &[]); + if let PipelineDocument::Run(held) = &mut started { + held.started_on = Date((*day).into()); + } + rig.mind.committed(vec![started]); + } +} + +#[test] +fn more_self_runs_started_today_than_the_read_holds_still_hold_the_brake() { + let rig = Rig::new(vec![], &[says(&[])]); + let runs: Vec<(String, &str)> = (0..RECENT_RUNS + 3).map(|n| (format!("self-{n}"), "2026-10-09")).collect(); + self_runs_admitted_in_order(&rig, &runs); + assert_eq!(failure_of(rig.go()), None); + assert!(told(&rig)[0].trim_end().ends_with(WAKE_QUESTION)); +} + +#[test] +fn the_newest_self_run_holds_the_brake_beneath_a_full_read_of_older_days() { + let rig = Rig::new(vec![], &[says(&[])]); + let mut runs: Vec<(String, &str)> = (0..RECENT_RUNS + 2).map(|n| (format!("self-old-{n}"), "2026-10-08")).collect(); + runs.push(("self-new".into(), "2026-10-09")); + self_runs_admitted_in_order(&rig, &runs); + assert_eq!(failure_of(rig.go()), None); + assert!(told(&rig)[0].trim_end().ends_with(WAKE_QUESTION), "an oldest-first read would see only the older days"); +} + +#[test] +fn a_self_run_today_that_is_not_hers_does_not_hold_the_brake() { + let rig = Rig::new(vec![], &[says(&[])]); + rig.mind.committed(vec![run("self-operator", RunTurn::SelfRun, RunOperator::Operator, &[])]); + assert_eq!(failure_of(rig.go()), None); + assert!(told(&rig)[0].trim_end().ends_with(WAKE_ASK)); +} + +/// Answers every read but the one for her Self runs. +struct LosesSelfRuns<'a>(&'a TestMind); + +impl MindPort for LosesSelfRuns<'_> { + fn instance(&self) -> &Slug { + self.0.instance() + } + + fn query(&self, selection: &cultnet_rs::Selection) -> Result { + if selection.fields.iter().flatten().any(|field| field.index == "turn") { + bail!("the mind lost the read"); + } + self.0.query(selection) + } + + fn query_near(&self, selection: &cultnet_rs::Selection, text: &str, top_k: u32) -> Result { + self.0.query_near(selection, text, top_k) + } + + fn admit(&self, agent: &str, session: &str, documents: Vec) -> Result { + self.0.admit(agent, session, documents) + } +} + +#[test] +fn a_mind_that_cannot_answer_the_started_today_read_holds_the_brake() { + let rig = Rig::new(vec![], &[says(&[])]); + let losing = LosesSelfRuns(&rig.mind); + assert_eq!(failure_of(rig.start_over(&losing, &config())), None); + assert!(told(&rig)[0].trim_end().ends_with(WAKE_QUESTION)); +} + #[test] fn a_self_run_of_another_day_does_not_hold_the_brake() { let rig = Rig::new(vec![], &[says(&[])]);