From 6d51fd190f05d604403784d00b31a7fc5e74cb5d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 3 Nov 2026 12:47:16 +0100 Subject: [PATCH 1/3] connector: the plan line dates a reading by the minute it was taken, not by an age render_plan printed 'read 2m ago', rendered once per Persona process and restated under every Self brief, so the line was false within minutes of a session that lives up to four hours and cannot refresh it. It now prints 'read at 2026-11-03T11:20Z', true at any later moment. Nothing else in the prompt changes. Co-Authored-By: Claude Opus 5.5 --- crates/mind-body/src/connector.rs | 34 ++++++++++++++++++++------ crates/mind-body/src/persona/tests.rs | 4 +-- crates/mind-body/src/self_run/tests.rs | 4 ++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/crates/mind-body/src/connector.rs b/crates/mind-body/src/connector.rs index 37acb5d..e0846da 100644 --- a/crates/mind-body/src/connector.rs +++ b/crates/mind-body/src/connector.rs @@ -21,8 +21,14 @@ fn resets(at_unix_ms: u64, now_ms: u64) -> String { if at_unix_ms > now_ms { format!("resets at {at} (in {})", span(at_unix_ms - now_ms)) } else { format!("resets at {at} (already past)") } } +/// The minute a reading was taken, as a UTC time. An instant, not an age: the line stays true when +/// it is read again hours later in a session that cannot refresh it. +fn read_at(at_unix_ms: u64) -> String { + i64::try_from(at_unix_ms).ok().and_then(DateTime::::from_timestamp_millis).map_or_else(|| at_unix_ms.to_string(), |at| at.format("%Y-%m-%dT%H:%MZ").to_string()) +} + /// The plan windows as one line for a Mind: each window's percent used, its -/// status, when it resets and how old the reading is. Rendered from the readout +/// status, when it resets and the minute the reading was taken. Rendered from the readout /// and nothing else, so no number in it is ever a paraphrase; the guidance that /// follows it states the swarm's aim and that nothing enforces it. pub fn render_plan(windows: &[PlanQuota], now_ms: u64) -> String { @@ -34,13 +40,13 @@ pub fn render_plan(windows: &[PlanQuota], now_ms: u64) -> String { .iter() .map(|plan| match plan.utilization_permille { Some(permille) => format!( - "{} {}.{}% {}, {}, read {} ago", + "{} {}.{}% {}, {}, read at {}", plan.window, permille / 10, permille % 10, plan.status, resets(plan.resets_at_unix_ms, now_ms), - span(now_ms.saturating_sub(plan.observed_at_unix_ms)) + read_at(plan.observed_at_unix_ms) ), None => format!("{} expired, {}", plan.window, resets(plan.resets_at_unix_ms, now_ms)), }) @@ -60,17 +66,29 @@ mod tests { fn a_window_reads_as_a_time_and_spans_not_unix_milliseconds() { let now = 1_793_307_000_000 - (4 * 86_400 + 11 * 3600 + 30 * 60) * 1000; let line = render_plan(&[window(Some(423), 1_793_307_000_000, now - 385_639_000)], now); - assert!(line.starts_with("Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 4d 11h), read 4d 11h ago. "), "{line}"); + assert!(line.starts_with("Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 4d 11h), read at 2026-10-20T22:12Z. "), "{line}"); assert!(!line.contains("unix ms"), "{line}"); } #[test] fn spans_use_the_two_largest_units_and_a_passed_reset_says_so() { let reset = 1_793_307_000_000; - assert!(render_plan(&[window(Some(10), reset, reset - 5000)], reset - 5000).contains("(in 5s), read 0s ago")); - assert!(render_plan(&[window(Some(10), reset, reset - 720_000)], reset - 600_000).contains("(in 10m), read 2m ago")); - assert!(render_plan(&[window(Some(10), reset, reset)], reset).contains("(already past), read 0s ago"), "a reset at this very moment is past"); - assert!(render_plan(&[window(Some(10), reset, reset)], reset + 1000).contains("(already past), read 1s ago")); + assert!(render_plan(&[window(Some(10), reset, reset - 5000)], reset - 5000).contains("(in 5s), read at 2026-10-29T20:49Z")); + assert!(render_plan(&[window(Some(10), reset, reset - 720_000)], reset - 600_000).contains("(in 10m), read at 2026-10-29T20:38Z")); + assert!(render_plan(&[window(Some(10), reset, reset)], reset).contains("(already past), read at 2026-10-29T20:50Z"), "a reset at this very moment is past"); + assert!(render_plan(&[window(Some(10), reset, reset)], reset + 1000).contains("(already past), read at 2026-10-29T20:50Z")); assert!(render_plan(&[window(None, reset, reset)], reset - 12_000_000).contains("five_hour expired, resets at 2026-10-29T20:50:00Z (in 3h 20m)")); } + #[test] + fn the_reading_is_dated_by_the_minute_it_was_taken_and_the_line_does_not_age() { + let reset = 1_793_307_000_000; + let taken = 1_793_306_999_000; // 20:49:59Z: the minute is cut, not rounded + let first = render_plan(&[window(Some(10), reset, taken)], taken); + assert!(first.contains(", read at 2026-10-29T20:49Z. "), "{first}"); + let hours_later = render_plan(&[window(Some(10), reset, taken)], taken + 3 * 3_600_000); + assert!(hours_later.contains(", read at 2026-10-29T20:49Z. "), "{hours_later}"); + assert!(!first.contains(" ago") && !hours_later.contains(" ago"), "{first} {hours_later}"); + assert!(render_plan(&[window(Some(10), reset, 0)], reset).contains(", read at 1970-01-01T00:00Z. ")); + assert!(render_plan(&[window(Some(10), reset, u64::MAX)], reset).contains(&format!(", read at {}. ", u64::MAX)), "an instant out of range is printed as the number it is"); + } } diff --git a/crates/mind-body/src/persona/tests.rs b/crates/mind-body/src/persona/tests.rs index 4011e89..453dba1 100644 --- a/crates/mind-body/src/persona/tests.rs +++ b/crates/mind-body/src/persona/tests.rs @@ -3511,7 +3511,7 @@ Near what was said: - (2026-10-09, in #swarm, Did): "Closed the old bridge" -Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read 0s ago. The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo. +Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read at 2026-10-29T20:33Z. The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo. Tempo: not yet read. The swarm beat is the lower median of the votes cast: a Mind with no standing vote abstains and is not counted, and with no vote cast the beat is 900 s. A vote only needs changing when conditions change; leave it standing otherwise. You vote with tempo_vote_beat_s (60 to 86400 seconds per beat; omit the key to leave your standing vote as it is, and send null to withdraw it and abstain) and declare your own urgency with urgency_permille (0 to 1000, null for none); an urgency lasts until your next turn, so use it as etiquette, not as a habit. @@ -3538,7 +3538,7 @@ What you remember (from your own mind; `huginn view ` reads a record): - nothing is remembered yet. -Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read 0s ago. The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo. +Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read at 2026-10-29T20:33Z. The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo. Tempo: not yet read. The swarm beat is the lower median of the votes cast: a Mind with no standing vote abstains and is not counted, and with no vote cast the beat is 900 s. A vote only needs changing when conditions change; leave it standing otherwise. You vote with tempo_vote_beat_s (60 to 86400 seconds per beat; omit the key to leave your standing vote as it is, and send null to withdraw it and abstain) and declare your own urgency with urgency_permille (0 to 1000, null for none); an urgency lasts until your next turn, so use it as etiquette, not as a habit. diff --git a/crates/mind-body/src/self_run/tests.rs b/crates/mind-body/src/self_run/tests.rs index 3c2b76e..49d5659 100644 --- a/crates/mind-body/src/self_run/tests.rs +++ b/crates/mind-body/src/self_run/tests.rs @@ -357,7 +357,7 @@ fn the_start_message_is_the_first_thing_written_and_names_the_skill_the_instance let ran = go(vec![Out(result_line())]); assert!(matches!(&ran.events[0], Event::Wrote(_)), "the start precedes the first read: {:?}", ran.events[0]); let written = ran.written(); - for needle in ["Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read 0s ago", "about 90% of the weekly allowance", "nothing enforces it"] { + for needle in ["Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read at 2026-10-29T20:33Z", "about 90% of the weekly allowance", "nothing enforces it"] { assert!(written[0].contains(needle), "the lease grant plan windows reach the child: {needle}: {}", written[0]); } for needle in ["Use the eureka skill.", "yggdrasil", "yggdrasil:run:selfrun-a", AGENT] { @@ -1376,6 +1376,8 @@ fn a_brief_derived_while_a_turn_runs_is_written_at_once_counted_at_its_echo_and_ assert_eq!(told.len(), 1, "{:?}", ran.written()); assert!(told[0].contains(&brief_ref("b1").id.0) && told[0].contains("Survey the noise functions."), "{}", told[0]); assert!(told[0].contains("resolve follow-up") && told[0].contains("Recorded"), "she is told how to close it: {}", told[0]); + // The plan line is restated under every brief for the run's life, so it names the instant of the reading, never an age that stops being true. + assert!(told[0].contains("read at 2026-10-29T20:33Z") && !told[0].contains(" ago"), "{}", told[0]); assert_eq!(ran.written().len(), 2, "the start message and one tell: {:?}", ran.written()); assert!(ran.out.contains("decision Write { tells: true, flush: None } prefix 10 ttl_s 0 idle_s working inbox true written 2 consumed 1\n"), "{}", ran.out); assert_eq!(ran.count(&Event::InputClosed), 0); From 335ae4dca33f03aac3292b1d538264971f8b0393 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 3 Nov 2026 12:56:06 +0100 Subject: [PATCH 2/3] connector: the plan line prints the reset as an instant only '(in 4d 11h)' and '(already past)' were computed at render time and as frozen as the read age. The absolute reset time stays. Co-Authored-By: Claude Opus 5.5 --- crates/mind-body/src/connector.rs | 37 +++++++------------------- crates/mind-body/src/persona/tests.rs | 4 +-- crates/mind-body/src/self_run/tests.rs | 2 +- 3 files changed, 13 insertions(+), 30 deletions(-) diff --git a/crates/mind-body/src/connector.rs b/crates/mind-body/src/connector.rs index e0846da..3ca7688 100644 --- a/crates/mind-body/src/connector.rs +++ b/crates/mind-body/src/connector.rs @@ -4,21 +4,11 @@ use chrono::{DateTime, SecondsFormat, Utc}; use model_connector::PlanQuota; -/// A span in the two largest units that fit: `45s`, `12m`, `3h 20m`, `4d 11h`. -fn span(ms: u64) -> String { - let seconds = ms / 1000; - match seconds { - 0..=59 => format!("{seconds}s"), - 60..=3599 => format!("{}m", seconds / 60), - 3600..=86399 => format!("{}h {}m", seconds / 3600, seconds % 3600 / 60), - _ => format!("{}d {}h", seconds / 86400, seconds % 86400 / 3600), - } -} - -/// When a window resets: the UTC time, and how far off it is from `now_ms`. -fn resets(at_unix_ms: u64, now_ms: u64) -> String { +/// When a window resets: the UTC time alone. An instant is true when read again hours later; "in 4d 11h" +/// or "already past" is not. +fn resets(at_unix_ms: u64) -> String { let at = i64::try_from(at_unix_ms).ok().and_then(DateTime::::from_timestamp_millis).map_or_else(|| at_unix_ms.to_string(), |at| at.to_rfc3339_opts(SecondsFormat::Secs, true)); - if at_unix_ms > now_ms { format!("resets at {at} (in {})", span(at_unix_ms - now_ms)) } else { format!("resets at {at} (already past)") } + format!("resets at {at}") } /// The minute a reading was taken, as a UTC time. An instant, not an age: the line stays true when @@ -31,7 +21,7 @@ fn read_at(at_unix_ms: u64) -> String { /// status, when it resets and the minute the reading was taken. Rendered from the readout /// and nothing else, so no number in it is ever a paraphrase; the guidance that /// follows it states the swarm's aim and that nothing enforces it. -pub fn render_plan(windows: &[PlanQuota], now_ms: u64) -> String { +pub fn render_plan(windows: &[PlanQuota], _now_ms: u64) -> String { let guidance = "The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo."; if windows.is_empty() { return format!("Plan usage: unobserved. {guidance}"); @@ -45,10 +35,10 @@ pub fn render_plan(windows: &[PlanQuota], now_ms: u64) -> String { permille / 10, permille % 10, plan.status, - resets(plan.resets_at_unix_ms, now_ms), + resets(plan.resets_at_unix_ms), read_at(plan.observed_at_unix_ms) ), - None => format!("{} expired, {}", plan.window, resets(plan.resets_at_unix_ms, now_ms)), + None => format!("{} expired, {}", plan.window, resets(plan.resets_at_unix_ms)), }) .collect(); format!("Plan usage: {}. {guidance}", rendered.join("; ")) @@ -63,22 +53,15 @@ mod tests { } #[test] - fn a_window_reads_as_a_time_and_spans_not_unix_milliseconds() { + fn a_window_reads_as_a_time_not_unix_milliseconds() { let now = 1_793_307_000_000 - (4 * 86_400 + 11 * 3600 + 30 * 60) * 1000; let line = render_plan(&[window(Some(423), 1_793_307_000_000, now - 385_639_000)], now); - assert!(line.starts_with("Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 4d 11h), read at 2026-10-20T22:12Z. "), "{line}"); + assert!(line.starts_with("Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z, read at 2026-10-20T22:12Z. "), "{line}"); assert!(!line.contains("unix ms"), "{line}"); } #[test] - fn spans_use_the_two_largest_units_and_a_passed_reset_says_so() { - let reset = 1_793_307_000_000; - assert!(render_plan(&[window(Some(10), reset, reset - 5000)], reset - 5000).contains("(in 5s), read at 2026-10-29T20:49Z")); - assert!(render_plan(&[window(Some(10), reset, reset - 720_000)], reset - 600_000).contains("(in 10m), read at 2026-10-29T20:38Z")); - assert!(render_plan(&[window(Some(10), reset, reset)], reset).contains("(already past), read at 2026-10-29T20:50Z"), "a reset at this very moment is past"); - assert!(render_plan(&[window(Some(10), reset, reset)], reset + 1000).contains("(already past), read at 2026-10-29T20:50Z")); - assert!(render_plan(&[window(None, reset, reset)], reset - 12_000_000).contains("five_hour expired, resets at 2026-10-29T20:50:00Z (in 3h 20m)")); - } +fn the_reset_is_an_instant_whatever_the_moment_of_rendering() { let reset = 1_793_307_000_000; let at = |now| render_plan(&[window(Some(10), reset, reset - 5000)], now); let before = at(reset - 5000); assert!(before.contains("resets at 2026-10-29T20:50:00Z, read at 2026-10-29T20:49Z"), "{before}"); for now in [reset - 12_000_000, reset, reset + 1000] { assert_eq!(at(now), before, "the line does not depend on when it is rendered"); } assert!(!before.contains("(in ") && !before.contains("already past"), "{before}"); assert_eq!(render_plan(&[window(None, reset, reset)], reset - 12_000_000).split(". ").next().unwrap(), "Plan usage: five_hour expired, resets at 2026-10-29T20:50:00Z"); } #[test] fn the_reading_is_dated_by_the_minute_it_was_taken_and_the_line_does_not_age() { let reset = 1_793_307_000_000; diff --git a/crates/mind-body/src/persona/tests.rs b/crates/mind-body/src/persona/tests.rs index 453dba1..9ab6709 100644 --- a/crates/mind-body/src/persona/tests.rs +++ b/crates/mind-body/src/persona/tests.rs @@ -3511,7 +3511,7 @@ Near what was said: - (2026-10-09, in #swarm, Did): "Closed the old bridge" -Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read at 2026-10-29T20:33Z. The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo. +Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z, read at 2026-10-29T20:33Z. The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo. Tempo: not yet read. The swarm beat is the lower median of the votes cast: a Mind with no standing vote abstains and is not counted, and with no vote cast the beat is 900 s. A vote only needs changing when conditions change; leave it standing otherwise. You vote with tempo_vote_beat_s (60 to 86400 seconds per beat; omit the key to leave your standing vote as it is, and send null to withdraw it and abstain) and declare your own urgency with urgency_permille (0 to 1000, null for none); an urgency lasts until your next turn, so use it as etiquette, not as a habit. @@ -3538,7 +3538,7 @@ What you remember (from your own mind; `huginn view ` reads a record): - nothing is remembered yet. -Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read at 2026-10-29T20:33Z. The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo. +Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z, read at 2026-10-29T20:33Z. The swarm aims to have used about 90% of the weekly allowance at reset; nothing enforces it; you and the other Minds set the tempo. Tempo: not yet read. The swarm beat is the lower median of the votes cast: a Mind with no standing vote abstains and is not counted, and with no vote cast the beat is 900 s. A vote only needs changing when conditions change; leave it standing otherwise. You vote with tempo_vote_beat_s (60 to 86400 seconds per beat; omit the key to leave your standing vote as it is, and send null to withdraw it and abstain) and declare your own urgency with urgency_permille (0 to 1000, null for none); an urgency lasts until your next turn, so use it as etiquette, not as a habit. diff --git a/crates/mind-body/src/self_run/tests.rs b/crates/mind-body/src/self_run/tests.rs index 49d5659..3b36761 100644 --- a/crates/mind-body/src/self_run/tests.rs +++ b/crates/mind-body/src/self_run/tests.rs @@ -357,7 +357,7 @@ fn the_start_message_is_the_first_thing_written_and_names_the_skill_the_instance let ran = go(vec![Out(result_line())]); assert!(matches!(&ran.events[0], Event::Wrote(_)), "the start precedes the first read: {:?}", ran.events[0]); let written = ran.written(); - for needle in ["Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z (in 20d 7h), read at 2026-10-29T20:33Z", "about 90% of the weekly allowance", "nothing enforces it"] { + for needle in ["Plan usage: five_hour 42.3% allowed, resets at 2026-10-29T20:50:00Z, read at 2026-10-29T20:33Z", "about 90% of the weekly allowance", "nothing enforces it"] { assert!(written[0].contains(needle), "the lease grant plan windows reach the child: {needle}: {}", written[0]); } for needle in ["Use the eureka skill.", "yggdrasil", "yggdrasil:run:selfrun-a", AGENT] { From 7ac4098ff1fa3e0b36c4aa1ec9a8e5779997b29f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 3 Nov 2026 12:57:20 +0100 Subject: [PATCH 3/3] public surface: render_plan's unused clock is named _now_ms Co-Authored-By: Claude Opus 5.5 --- crates/mind-body/tests/public_surface.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/mind-body/tests/public_surface.txt b/crates/mind-body/tests/public_surface.txt index 7570af6..74699e4 100644 --- a/crates/mind-body/tests/public_surface.txt +++ b/crates/mind-body/tests/public_surface.txt @@ -41,7 +41,7 @@ mind_body::chat_rudp: struct RudpChat pub-fields[] mind_body::cli: fn instance_and_endpoint (args : & [String] , env_endpoint : Option < String > , usage : & str) -> Result < (Slug , SocketAddr) > mind_body::cli: fn operator_name (sudo_user : Option < String > , user : Option < String >) -> String mind_body::cli: fn run (args : & [String] , root : & Path , set_by : & str , now : DateTime < Utc > , out : & mut impl Write , stop_units : & mut dyn FnMut (StopScope < '_ >) -> Result < () > ,) -> Result < () > -mind_body::connector: fn render_plan (windows : & [PlanQuota] , now_ms : u64) -> String +mind_body::connector: fn render_plan (windows : & [PlanQuota] , _now_ms : u64) -> String mind_body::control: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum HeldReason { BrakeAbsent , BrakeHeld , Undecodable , } mind_body::control: # [derive (Debug , PartialEq , Eq)] pub enum Effective { Held { reason : HeldReason } , Released (Grant) , } mind_body::control: # [derive (Debug , PartialEq , Eq)] pub enum SwarmEffective { Held (HeldReason) , Released , }