Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
Meta Vens
7ac4098ff1 public surface: render_plan's unused clock is named _now_ms
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:57:20 +01:00
Meta Vens
335ae4dca3 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 <noreply@anthropic.com>
2026-11-03 12:56:06 +01:00
Meta Vens
6d51fd190f 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 <noreply@anthropic.com>
2026-11-03 12:47:16 +01:00
4 changed files with 34 additions and 31 deletions

View file

@ -4,28 +4,24 @@
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 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::<Utc>::from_timestamp_millis).map_or_else(|| at_unix_ms.to_string(), |at| at.to_rfc3339_opts(SecondsFormat::Secs, true));
format!("resets at {at}")
}
/// 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 {
let at = i64::try_from(at_unix_ms).ok().and_then(DateTime::<Utc>::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)") }
/// 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::<Utc>::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 {
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}");
@ -34,15 +30,15 @@ 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))
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("; "))
@ -57,20 +53,25 @@ 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 4d 11h ago. "), "{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() {
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;
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(None, reset, reset)], reset - 12_000_000).contains("five_hour expired, resets at 2026-10-29T20:50:00Z (in 3h 20m)"));
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");
}
}

View file

@ -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, 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 <id>` 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, 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.

View file

@ -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, 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);

View file

@ -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 , }