Persona wake: the brake counts only Self runs the Mind started itself #4
2 changed files with 129 additions and 5 deletions
|
|
@ -132,6 +132,8 @@ pub const ENV_DOCTRINE: &str = "MIND_PERSONA_DOCTRINE";
|
|||
pub const DOCTRINE_MAX_BYTES: usize = 96 * 1024;
|
||||
/// How many of her latest runs a turn's input lists.
|
||||
const RECENT_RUNS: u32 = 5;
|
||||
/// How many of her newest Self runs the started-today read takes: the query's page bound.
|
||||
const SELF_RUNS_READ: u32 = 200;
|
||||
/// The most bytes the responsibilities section of a turn's input carries (stewardships always whole).
|
||||
const GOALS_MAX_BYTES: usize = 6 * 1024;
|
||||
/// The room kept inside that bound for the rulings the shown items cite.
|
||||
|
|
@ -2169,17 +2171,39 @@ fn recent_runs(mind: &dyn MindPort, instance: &Slug) -> Result<Vec<String>> {
|
|||
Ok(lines)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Whether the Mind has started a Self run of her own today (UTC), one that failed to start included. A run that came from
|
||||
/// something said in chat does not count: it claims a follow-up `start_self_run` labelled by the batch of deliveries that
|
||||
/// prompted it (ruling `eureka-body:ruling:unasked-work-brake-ruled`). Her newest `SELF_RUNS_READ` Self runs are read, newest
|
||||
/// first, so the answer rests on the read's order; a read full of today's chat-started runs cannot show whether an older
|
||||
/// one is hers, so it answers yes and the wake question holds.
|
||||
fn started_self_today(mind: &dyn MindPort, instance: &Slug, now: DateTime<Utc>) -> Result<bool> {
|
||||
let turn = format!("{:?}", RunTurn::SelfRun);
|
||||
let operator = format!("{:?}", RunOperator::Mind);
|
||||
let runs = any_of(any_of(any_of(of_kind(PipelineKind::Run), "root", &[&instance.0]), "turn", &[&turn]), "operated_by", &[&operator]);
|
||||
let selection = Selection { descending: true, limit: Some(RECENT_RUNS), ..runs };
|
||||
let selection = Selection { descending: true, limit: Some(SELF_RUNS_READ), ..runs };
|
||||
let PipelinePageItems::Headers(runs) = mind.query(&selection)?.items else { bail!("the mind answered documents to a header selection") };
|
||||
let today = date(now);
|
||||
Ok(runs.iter().any(|run| matches!(&run.facts, PipelineFacts::Run { started_on, .. } if *started_on == today)))
|
||||
let full = runs.len() >= SELF_RUNS_READ as usize;
|
||||
let mut reached_older_day = false;
|
||||
for run in &runs {
|
||||
let PipelineFacts::Run { started_on, claims, .. } = &run.facts else { continue };
|
||||
if *started_on != today {
|
||||
reached_older_day = true;
|
||||
continue;
|
||||
}
|
||||
if !claims.iter().any(is_chat_started_claim) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(full && !reached_older_day)
|
||||
}
|
||||
|
||||
/// Whether a claim is the follow-up of a Self run started from chat: `start_self_run` labels it `chat-` and the batch's
|
||||
/// `s<first>-<last>` delivery sequences. A wake has no deliveries, so its batch is named by the Persona run, never `s<digits>`.
|
||||
fn is_chat_started_claim(claim: &PipelineRef) -> bool {
|
||||
let label = claim.id.0.rsplit(':').next().unwrap_or_default();
|
||||
let Some(sequences) = label.strip_prefix("chat-s") else { return false };
|
||||
matches!(sequences.split_once('-'), Some((first, last)) if !first.is_empty() && !last.is_empty() && first.bytes().chain(last.bytes()).all(|b| b.is_ascii_digit()))
|
||||
}
|
||||
|
||||
/// A run's latest resolution, if it has one: its sequence, id and outcome. A run with none is open.
|
||||
|
|
|
|||
|
|
@ -2901,6 +2901,106 @@ fn the_newest_self_run_holds_the_brake_beneath_a_full_read_of_older_days() {
|
|||
assert!(told(&rig)[0].trim_end().ends_with(WAKE_QUESTION), "an oldest-first read would see only the older days");
|
||||
}
|
||||
|
||||
fn starts_a_self_run() -> String {
|
||||
json!({ "intent": "Work it", "say": [], "start_self_run": { "brief": "Work", "repo": REPO, "campaign": CAMPAIGN, "for_item": null }, "remember": [], "settle": [], "urgency_permille": null }).to_string()
|
||||
}
|
||||
|
||||
/// A rig whose first session starts a Self run, from a chat message or from a quiet wake, then a quiet wake that is told.
|
||||
fn wake_after_a_self_run_started_from(deliveries: Vec<ChatDelivery>) -> String {
|
||||
let rig = Rig::new(deliveries, &[starts_a_self_run(), says(&[])]);
|
||||
let Outcome::Served { self_run, failure, .. } = rig.go() else { panic!("served") };
|
||||
assert_eq!((failure, self_run.is_some()), (None, true));
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
told(&rig).last().cloned().expect("a wake was told")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_self_run_started_from_chat_does_not_hold_the_ask() {
|
||||
let line = wake_after_a_self_run_started_from(vec![operator_says(1, "please start")]);
|
||||
assert!(line.trim_end().ends_with(WAKE_ASK), "{line}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_self_run_started_from_a_quiet_wake_holds_the_ask() {
|
||||
let line = wake_after_a_self_run_started_from(vec![]);
|
||||
assert!(line.trim_end().ends_with(WAKE_QUESTION), "{line}");
|
||||
}
|
||||
|
||||
/// A Self run of hers claiming a follow-up of the given label, both admitted.
|
||||
fn self_run_claiming(rig: &Rig, label: &str, follow_up_label: &str) {
|
||||
let source = run_ref("persona-source");
|
||||
if rig.mind.view(K::Run, &source.id.0).is_none() {
|
||||
rig.mind.committed(vec![run("persona-source", RunTurn::PersonaTurn, RunOperator::Mind, &[])]);
|
||||
rig.mind.committed(vec![close(source.clone(), recorded())]);
|
||||
}
|
||||
rig.mind.committed(vec![follow_up(follow_up_label, &source)]);
|
||||
let id = rig.mind.ids(K::FollowUp).into_iter().find(|id| id.ends_with(follow_up_label)).expect("the follow-up");
|
||||
rig.mind.committed(vec![run(label, RunTurn::SelfRun, RunOperator::Mind, &[reference(K::FollowUp, &id)])]);
|
||||
rig.mind.committed(vec![close(run_ref(label), recorded())]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_run_of_hers_beneath_chat_started_runs_holds_the_ask() {
|
||||
let rig = Rig::new(vec![], &[says(&[])]);
|
||||
self_run_claiming(&rig, "self-a-wake", "chat-mind-20261104T000000000Z-t1");
|
||||
for n in 0..RECENT_RUNS + 2 {
|
||||
self_run_claiming(&rig, &format!("self-b-chat-{n}"), &format!("chat-s{n}-{n}"));
|
||||
}
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
assert!(told(&rig)[0].trim_end().ends_with(WAKE_QUESTION), "an older wake-started run today still holds the ask");
|
||||
}
|
||||
|
||||
fn chat_started_runs_today(rig: &Rig, count: u32) {
|
||||
for n in 0..count {
|
||||
self_run_claiming(rig, &format!("self-chat-{n:03}"), &format!("chat-s{n}-{n}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_read_full_of_chat_started_runs_today_holds_the_ask_since_an_older_run_may_be_hers() {
|
||||
let rig = Rig::new(vec![], &[says(&[])]);
|
||||
chat_started_runs_today(&rig, SELF_RUNS_READ);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
assert!(told(&rig)[0].trim_end().ends_with(WAKE_QUESTION));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_full_read_that_reaches_an_older_day_has_seen_every_run_today() {
|
||||
let rig = Rig::new(vec![], &[says(&[])]);
|
||||
self_runs_admitted_in_order(&rig, &[("self-0-old".into(), "2026-10-08")]);
|
||||
chat_started_runs_today(&rig, SELF_RUNS_READ - 1);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
assert!(told(&rig)[0].trim_end().ends_with(WAKE_ASK));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_newest_runs_are_read_not_the_oldest() {
|
||||
let rig = Rig::new(vec![], &[says(&[])]);
|
||||
let old: Vec<(String, &str)> = (0..SELF_RUNS_READ + 5).map(|n| (format!("self-old-{n:03}"), "2026-10-08")).collect();
|
||||
self_runs_admitted_in_order(&rig, &old);
|
||||
self_runs_admitted_in_order(&rig, &[("self-new".into(), "2026-10-09")]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
assert!(told(&rig)[0].trim_end().ends_with(WAKE_QUESTION));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_chat_started_runs_today_leave_the_ask() {
|
||||
let rig = Rig::new(vec![], &[says(&[])]);
|
||||
self_run_claiming(&rig, "self-chat", "chat-s12-14");
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
assert!(told(&rig)[0].trim_end().ends_with(WAKE_ASK));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_claim_is_chat_started_only_when_the_batch_is_delivery_sequences() {
|
||||
let claim = |id: &str| PipelineRef { kind: K::FollowUp, id: id.into() };
|
||||
assert!(is_chat_started_claim(&claim("cm:follow_up:chat-s1-1")));
|
||||
assert!(is_chat_started_claim(&claim("cm:follow_up:chat-s1650-1651")));
|
||||
for other in ["cm:follow_up:chat-mind-20261104T1Z-t1", "cm:follow_up:chat-s-1", "cm:follow_up:chat-s1-", "cm:follow_up:chat-s1-x", "cm:follow_up:chat-s1", "cm:follow_up:brief-s1-1", ""] {
|
||||
assert!(!is_chat_started_claim(&claim(other)), "{other}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_self_run_today_that_is_not_hers_does_not_hold_the_brake() {
|
||||
let rig = Rig::new(vec![], &[says(&[])]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue