From a6302f0dd9a6e08a571aa19cdf2a80373b7b6ab7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 31 Oct 2026 05:58:44 +0100 Subject: [PATCH] tests: restore the self_run ceiling and flush tests and the persona DM-room, flag and outcome tests on the usage-only surface; close the red tests; regenerate public_surface.txt; strip stale initiative and dial wording Co-Authored-By: Claude Sonnet 5.5 --- crates/eureka-state/tests/cli.rs | 5 +- crates/mind-body/README.md | 2 +- crates/mind-body/src/heartbeat.rs | 6 +- crates/mind-body/src/heartbeat/tests.rs | 3 +- crates/mind-body/src/persona/tests.rs | 160 ++++++++++++++++++++++ crates/mind-body/src/self_run/tests.rs | 152 ++++++++++++++++++++ crates/mind-body/tests/cli.rs | 19 +-- crates/mind-body/tests/public_surface.txt | 98 +++++-------- 8 files changed, 361 insertions(+), 84 deletions(-) diff --git a/crates/eureka-state/tests/cli.rs b/crates/eureka-state/tests/cli.rs index f2feb80..cb255dd 100644 --- a/crates/eureka-state/tests/cli.rs +++ b/crates/eureka-state/tests/cli.rs @@ -971,9 +971,10 @@ fn schema_prints_the_published_files() { let names = run(&[], &["schema"], None); assert_eq!(names.code, 0); let names: Vec<&str> = names.out.lines().collect(); - assert_eq!(names.len(), 15, "{names:?}"); + assert_eq!(names.len(), 16, "{names:?}"); assert_eq!(names[0], "campaign"); - assert_eq!(names[14], "selection"); + assert_eq!(names[14], "pulse"); + assert_eq!(names[15], "selection"); for kind in PipelineKind::ALL { assert!(names.contains(&kind.name()), "{}", kind.name()); } diff --git a/crates/mind-body/README.md b/crates/mind-body/README.md index d919e53..9645ff1 100644 --- a/crates/mind-body/README.md +++ b/crates/mind-body/README.md @@ -12,7 +12,7 @@ wrong or the mind cannot be reached. ## The heartbeat -`mind-heartbeat` is the one process that decides who wakes and when: acting burns initiative by spend, the classifier's urgency multiplies how fast it regains, and the operator tagging or naming a Persona fills it. `mind-heartbeat timeline` prints its latest record. +`mind-heartbeat` is the one process that decides who wakes and when: each beat falls when the Minds' voted tempo has passed and goes to the Mind that has waited longest, bent by the urgency she declares and a member addressing her; the operator tagging or naming a Persona launches her at once. `mind-heartbeat timeline` prints its latest record. The actuator has its own ignored test, `the_real_bin_finds_her_run_...` in `tests/self_run.rs`: the real `mind-self-run` against the real daemon with a diff --git a/crates/mind-body/src/heartbeat.rs b/crates/mind-body/src/heartbeat.rs index b277715..6a60c77 100644 --- a/crates/mind-body/src/heartbeat.rs +++ b/crates/mind-body/src/heartbeat.rs @@ -10,7 +10,7 @@ //! and (a later cut) topics she subscribed to. Two things launch a Persona off //! the beat: the operator tagging or naming her, once per message, and her own //! Self run having ended since her last turn. Nothing is metered here: no -//! dollars, no budget, no model call. +//! meter and no model call. //! //! The heartbeat owns `heartbeat.cc` and is its only writer; Persona units read //! it. The brakes stay the operator's and are read on every poll. Busy and idle @@ -49,7 +49,7 @@ const PENDING_PAGES: usize = 16; /// Who a Persona is, for the two things the heartbeat matches on: the Matrix /// user id its tags carry, and the names a message may call it by. Each Mind is /// its own Huginn daemon, so the entry also carries the daemon the Persona's -/// control, initiative and launches are read from. +/// control, runs and launches are read from. #[derive(Clone, Debug, PartialEq, Eq)] pub struct PersonaId { pub slug: Slug, @@ -203,7 +203,7 @@ pub trait TempoView { /// The store as a CultCache single-file store under the unit's state directory. /// It registers the state and the trace and nothing else: a file still holding -/// the dollar initiative documents does not decode, and every use of it fails +/// any older document does not decode, and every use of it fails /// naming the file. It is never migrated. pub struct FileStore { path: PathBuf, diff --git a/crates/mind-body/src/heartbeat/tests.rs b/crates/mind-body/src/heartbeat/tests.rs index 8e02834..d41558f 100644 --- a/crates/mind-body/src/heartbeat/tests.rs +++ b/crates/mind-body/src/heartbeat/tests.rs @@ -366,7 +366,8 @@ fn the_operator_naming_a_persona_launches_her_at_once_off_the_beat_and_once_per_ // Her turn fails without acking; the same message does not launch her again. rig.end_run(BOB); assert_eq!(rig.poll(), Outcome::Polled { launched: vec![] }); - assert_eq!(rig.runs(BOB), 3); + assert_eq!(rig.runs(BOB), 2, "her old turn and the one launch, no second launch for the same message"); + assert_eq!(rig.launched(), vec![Slug("bob".into())]); // A tag in m.mentions counts as naming. rig.say(ANN, delivery(2, "@op:chat", "please look", &["@ann:chat"])); assert_eq!(rig.poll(), Outcome::Polled { launched: vec![Slug("ann".into())] }); diff --git a/crates/mind-body/src/persona/tests.rs b/crates/mind-body/src/persona/tests.rs index a692a55..4ec4515 100644 --- a/crates/mind-body/src/persona/tests.rs +++ b/crates/mind-body/src/persona/tests.rs @@ -945,3 +945,163 @@ fn a_usage_readout_that_cannot_be_had_is_said_and_the_turn_goes_on() { assert_eq!((failure, answered), (None, 1), "usage is display only"); assert!(rig.connector.requests.borrow()[0].input.contains("Plan usage: unavailable.")); } + +#[test] +fn the_turn_is_one_tool_less_call_constrained_to_the_reply_schema_with_her_rooms() { + let rig = Rig::new(vec![operator_says(1, "a")], &[says(&[])]); + rig.go(); + let requests = rig.connector.requests.borrow(); + assert_eq!(requests[0].caller, INSTANCE); + assert!(requests[0].instructions.contains("You are Eureka.")); + let schema: serde_json::Value = serde_json::from_str(&requests[0].output_schema_json).unwrap(); + assert_eq!(schema["properties"]["say"]["items"]["properties"]["room"]["enum"], json!(["swarm", "usage", "operator"])); +} + +#[test] +fn a_members_dm_is_answered_in_its_room_and_no_other_unconfigured_room_is() { + let dm = "!abc:chat"; + let rig = Rig::new(vec![direct(1, dm, "@m:chat", "hello, are you there")], &[says(&[(dm, "yes")])]); + let Outcome::Served { failure, answered, .. } = rig.go() else { panic!("served") }; + assert_eq!((failure, answered), (None, 1)); + assert_eq!(rig.chat.sent.borrow()[0].room_alias, dm); + let request = &rig.connector.requests.borrow()[0]; + let schema: serde_json::Value = serde_json::from_str(&request.output_schema_json).unwrap(); + assert_eq!(schema["properties"]["say"]["items"]["properties"]["room"]["enum"], json!(["swarm", "usage", "operator", dm])); + assert!(request.input.contains(dm), "the DM's room is among the rooms she is told she may speak in"); + + let rig = Rig::new(vec![direct(1, dm, "@m:chat", "hello")], &[says(&[("!other:chat", "psst")])]); + let Outcome::Served { failure, .. } = rig.go() else { panic!("served") }; + assert_eq!(failure, Some(Failure::RoomNotAllowed)); + assert!(rig.chat.sent.borrow().is_empty() && rig.chat.acked.borrow().is_empty()); + + let rig = Rig::new(vec![delivery(1, "!notdm:chat", "@m:chat", "hello")], &[says(&[("!notdm:chat", "psst")])]); + let Outcome::Served { failure, .. } = rig.go() else { panic!("served") }; + assert_eq!(failure, Some(Failure::RoomNotAllowed), "a room that is not a DM is hers only when configured"); +} + +#[test] +fn deliveries_from_different_dm_rooms_are_never_in_one_turn() { + let (a, b) = ("!a:chat", "!b:chat"); + let pending = vec![ + direct(1, a, "@ann:chat", "secret-a"), + direct(2, b, "@bob:chat", "hi-b"), + delivery(3, "swarm", "@m:chat", "public-c"), + direct(4, a, "@ann:chat", "more-a"), + direct(5, a, "@ann:chat", "again-a"), + ]; + let replies = [says(&[(a, "1")]), says(&[(b, "2")]), says(&[("swarm", "3")]), says(&[(a, "4")])]; + let rig = Rig::new(pending, &replies); + let Outcome::Served { failure, answered, .. } = rig.go() else { panic!("served") }; + assert_eq!((failure, answered), (None, 4)); + let inputs: Vec = rig.connector.requests.borrow().iter().map(|request| request.input.clone()).collect(); + let texts = ["secret-a", "hi-b", "public-c", "more-a"]; + for (turn, input) in inputs.iter().enumerate() { + for (other, text) in texts.iter().enumerate() { + assert_eq!(input.contains(text), turn == other, "turn {turn} text {text}"); + } + } + assert!(inputs[3].contains("again-a"), "one room's consecutive deliveries share its turn"); + assert!(!inputs[0].contains("again-a"), "a later delivery waits for its turn"); + assert_eq!(*rig.chat.acked.borrow(), vec![1, 2, 3, 5]); +} + +#[test] +fn outcomes_and_failures_read_as_one_line_each_without_values() { + assert_eq!(Outcome::Dead(HeldReason::BrakeHeld).to_string(), "dead: BrakeHeld"); + let served = Outcome::Served { run: run_ref("r1"), answered: 2, failure: Some(Failure::Send), self_run: Some(run_ref("s1")) }; + let line = served.to_string(); + assert!(line.contains("r1") && line.contains("answered 2") && line.contains("a send was refused or failed") && line.contains("self run") && line.contains("s1"), "{line}"); + assert_eq!(Failure::Held(HeldReason::BrakeAbsent).to_string(), "held: BrakeAbsent"); + let all = [ + Failure::Chat, + Failure::Persona, + Failure::Doctrine, + Failure::DoctrineTooLarge, + Failure::Connector, + Failure::Undecodable, + Failure::RoomNotAllowed, + Failure::Send, + Failure::SelfRun, + Failure::Pulse, + Failure::Ack, + ]; + for failure in all { + assert!(!failure.to_string().is_empty()); + } + let texts: std::collections::BTreeSet<_> = all.map(|failure| failure.to_string()).into(); + assert_eq!(texts.len(), all.len(), "each failure reads differently"); +} + +#[test] +fn a_turn_is_named_by_its_run_and_its_number() { + let rig = Rig::new(vec![operator_says(1, "a")], &(0..4).map(|_| says(&[("swarm", "x")])).collect::>()); + rig.mind.committed(vec![run("woken", RunTurn::PersonaTurn, RunOperator::Mind, &[])]); + rig.clock.0.set(600_000); + let (deliveries, next) = (rig.chat.deliveries.clone(), Cell::new(1)); + *rig.connector.during.borrow_mut() = Some(Box::new(move || { + next.set(next.get() + 1); + if next.get() < 3 { + deliveries.borrow_mut().push(operator_says(next.get(), "more")); + } + })); + let Outcome::Served { run: carried, answered, .. } = rig.start_with(&config()) else { panic!("served") }; + assert_eq!((carried, answered), (run_ref("woken"), 2)); + let ids: Vec<_> = rig.connector.requests.borrow().iter().map(|request| request.request_id.clone()).collect(); + assert_eq!(ids, ["woken-t0", "woken-t1"]); + let trace = rig.trace.0.borrow(); + assert_eq!(trace.iter().map(|record| (record.run.as_str(), record.turn)).collect::>(), [("woken", 0), ("woken", 1)]); +} + +#[test] +fn the_persona_flags_are_taken_out_and_the_rest_is_left_for_the_shared_parser() { + let args = |text: &str| text.split_whitespace().map(String::from).collect::>(); + let (parsed, rest) = Config::from_args( + &args("--instance eureka --rooms swarm,usage,operator --operator @op:chat,@op2:chat --self-sender @eureka:chat --endpoint rudp://10.0.0.1:1"), + "usage", + ) + .unwrap(); + assert_eq!(parsed.rooms, ["swarm", "usage", "operator"]); + assert_eq!((parsed.own_sender.as_str(), parsed.max_turns), ("@eureka:chat", MAX_TURNS_PER_START)); + assert_eq!(parsed.operators, ["@op:chat", "@op2:chat"]); + assert_eq!(rest, args("--instance eureka --endpoint rudp://10.0.0.1:1")); + let (two, _) = Config::from_args(&args("--rooms a --operator o --self-sender s --max-turns 2"), "usage").unwrap(); + assert_eq!(two.max_turns, 2); + for bad in [ + "--rooms a --operator o", + "--rooms a,,b --operator o --self-sender s", + "--rooms a --rooms a --operator o --self-sender s", + "--rooms a --operator o --self-sender s --max-turns 0", + "--rooms a --operator o --self-sender s --max-turns x", + "--rooms a --operator o --self-sender", + ] { + assert!(Config::from_args(&args(bad), "usage").is_err(), "{bad}"); + } +} + +#[test] +fn the_persona_flags_come_from_the_environment_when_the_command_line_lacks_them() { + let env = |name: &str| { + Some( + match name { + ENV_ROOMS => "swarm,usage", + ENV_OPERATOR => "@op:chat,@op2:chat", + ENV_SELF_SENDER => "@eureka:chat", + _ => "", + } + .to_string(), + ) + }; + let args = |text: &str| text.split_whitespace().map(String::from).collect::>(); + let (config, rest) = Config::from_args_and_env(&args("--instance eureka"), env, "usage").unwrap(); + assert_eq!( + config, + Config { rooms: vec!["swarm".into(), "usage".into()], operators: vec!["@op:chat".into(), "@op2:chat".into()], own_sender: "@eureka:chat".into(), max_turns: MAX_TURNS_PER_START, doctrine: vec![] } + ); + assert_eq!(rest, ["--instance", "eureka"]); + // A flag wins over its variable, one at a time. + let (flagged, _) = Config::from_args_and_env(&args("--instance eureka --rooms swarm"), env, "usage").unwrap(); + assert_eq!((flagged.rooms.len(), flagged.operators.len()), (1, 2)); + // An empty variable is unset, and a missing setting is the usage error. + assert!(Config::from_args_and_env(&args("--instance eureka"), |name| if name == ENV_ROOMS { Some(String::new()) } else { env(name) }, "usage").is_err()); + assert!(Config::from_args_and_env(&args("--instance eureka"), |_| None, "usage").is_err()); +} diff --git a/crates/mind-body/src/self_run/tests.rs b/crates/mind-body/src/self_run/tests.rs index 4cabed5..9390888 100644 --- a/crates/mind-body/src/self_run/tests.rs +++ b/crates/mind-body/src/self_run/tests.rs @@ -470,6 +470,41 @@ fn a_mind_that_cannot_be_read_or_a_child_that_cannot_start_is_an_error_not_an_ou // ---- what is started ------------------------------------------------------ +#[test] +fn claude_args_is_exactly_the_fixed_list_for_a_session() { + let session = Uuid::parse_str(SESSION_OF_RUN_A).unwrap(); + let args = claude_args(session); + assert_eq!( + args, + [ + "-p", + "--input-format", + "stream-json", + "--output-format", + "stream-json", + "--verbose", + "--session-id", + SESSION_OF_RUN_A, + "--max-turns", + "500", + "--permission-mode", + "bypassPermissions", + "--permission-prompts", + "none", + "--setting-sources", + "user", + ] + ); + // Those belong to the run measure or to the unit's managed settings. + for forbidden in ["--resume", "--continue", "--fork-session", "--model", "--dangerously-skip-permissions", "--settings", "--system-prompt", "--max-budget"] { + assert!(!args.iter().any(|arg| arg.starts_with(forbidden) || arg.contains("dangerously")), "{forbidden}"); + } + // Print mode starts in Manual without a mode, and prompts none then denies. + let mode = args.iter().position(|arg| arg == "--permission-mode").expect("mode flag"); + assert_eq!(args[mode + 1], "bypassPermissions"); + assert_eq!(MAX_TURNS, 500); +} + #[test] fn the_session_id_is_uuid_v5_of_the_run_id_and_differs_per_run() { assert_eq!(session_id(&foreign_run("eureka:run:mind-20261022T120000000Z")).to_string(), SESSION_OF_MIND_RUN); @@ -477,6 +512,18 @@ fn the_session_id_is_uuid_v5_of_the_run_id_and_differs_per_run() { assert_eq!(session_id(&run_ref("selfrun-b")).to_string(), SESSION_OF_RUN_B); } +#[test] +fn the_child_is_started_for_her_run_with_the_fixed_list_whatever_the_run_record_says() { + // The run's budget field is a dead record; nothing in what is started depends on it. + for budget in ["0", "0.5", "10", "100"] { + let mind = TestMind::seeded(); + mind.committed(vec![self_run("selfrun-b", budget)]); + let ran = carry_with(&Seen::of(&mind), vec![Out(result_line())], Some(0), false); + assert_eq!(ran.spawns.len(), 1, "{budget}"); + assert_eq!(ran.spawns[0], claude_args(Uuid::parse_str(SESSION_OF_RUN_B).unwrap()), "{budget}"); + } +} + #[test] fn the_start_message_is_the_first_thing_written_and_names_the_skill_the_instance_and_the_run() { let ran = go(vec![Out(result_line())]); @@ -647,6 +694,27 @@ fn read_usage_classifies_the_owners_real_records() { } } +#[test] +fn records_that_share_a_message_id_count_once_and_subagent_records_never_move_the_prefix() { + let mut steps: Vec = RECORDS.lines().map(|line| Out(line.to_string())).collect(); + steps.insert(3, Out(subagent("msg_sub", 999_999))); + let ran = go(steps); + assert_eq!( + ran.out, + "request 1 prefix 84210 +request 2 prefix 100751 +request 3 prefix 117174 +request 4 prefix 118336 +request 5 prefix 126746 +request 6 prefix 127250 +request 7 prefix 129853 +" + ); + let (cause, _, requests, prefix) = ran.ended(); + assert_eq!((cause, requests, prefix), (Cause::Own, 7, 129_853), "the newest prefix, 129853, not a subagent's 999999"); + assert_eq!(flushes(&ran), 0); +} + #[test] fn the_prefix_is_the_newest_requests_not_the_largest() { let ran = go(vec![Out(at("m1", 200_000)), Out(at("m2", 150_000))]); @@ -744,6 +812,41 @@ fn the_headroom_counts_the_requests_after_the_first_result_that_follows_the_flus assert_eq!(same_turn.count(&Event::Stopped(Stop::Interrupt)), 0); } +#[test] +fn a_jump_past_the_ceiling_plus_the_headroom_writes_the_flush_and_the_running_turn_cannot_use_up_the_flushs_turn() { + let jump = carry_with( + &Seen::of(&with_run()), + vec![Out(at("m1", 290_000)), Out(at("m2", 400_000)), Out(at("m3", 470_000)), Out(result_line()), Out(at("m4", 459_999)), Out(at("m5", 460_000)), Out(at("m6", 470_000))], + Some(130), + true, + ); + let kinds: Vec<&str> = jump + .events + .iter() + .filter_map(|event| match event { + Event::Wrote(line) if line.contains("context ceiling") => Some("flush"), + Event::Stopped(_) => Some("interrupt"), + Event::InputClosed => Some("closed"), + Event::Asked(_) => Some("read"), + _ => None, + }) + .collect(); + // Reads: m1, m2 (the jump, which writes the flush), m3 (the running turn's 470k, past the headroom: no interrupt). + assert_eq!(kinds[..5], ["read", "read", "flush", "read", "read"], "{kinds:?}"); + // The headroom is measured from the prefix the flush was asked at (400k) and counts the requests after the + // first result only: 459_999 does not interrupt; 460_000 does, once, and m6 is never read. + assert_eq!(jump.count(&Event::Stopped(Stop::Interrupt)), 1, "{kinds:?}"); + assert_eq!(jump.out, "request 1 prefix 290000 +request 2 prefix 400000 +request 3 prefix 470000 +result subtype=success is_error=true: Failed to authenticate: OAuth session expired and could not be refreshed +request 4 prefix 459999 +request 5 prefix 460000 +"); + assert_eq!(jump.ended().0, Cause::Signalled(Stop::Interrupt)); + assert_eq!(flushes(&jump), 1); +} + #[test] fn a_child_that_ends_before_the_grace_is_never_interrupted() { let ran = go(vec![Out(at("m1", CEILING_TOKENS)), Out(result_line())]); @@ -938,6 +1041,27 @@ fn the_outcome_names_how_it_ended_and_the_exit_status_follows_the_childs_code() // ---- the real child ----------------------------------------------------------- +#[test] +fn unreadable_and_foreign_lines_are_skipped_and_never_printed() { + let canary = "canary-stream-line-8c41"; + let mut foreign: Value = serde_json::from_str(&init_line()).unwrap(); + foreign["cwd"] = json!(canary); + foreign["type"] = json!(canary); + let ran = go(vec![ + Out(format!("not json {canary}")), + Out(foreign.to_string()), + Out(format!("[\"{canary}\"]")), + Out(at("m1", 10)), + Out(result_line()), + ]); + assert_eq!(ran.out, "request 1 prefix 10 +result subtype=success is_error=true: Failed to authenticate: OAuth session expired and could not be refreshed +"); + let outcome = ran.outcome.as_ref().unwrap().to_string(); + assert!(!ran.out.contains(canary) && !outcome.contains(canary)); + assert!(!ran.written().iter().any(|text| text.contains(canary)), "nothing from the stream is written back"); +} + #[cfg(unix)] mod process { mod lease_child; @@ -1107,6 +1231,34 @@ mod process { } } + #[test] + fn a_whole_run_through_the_real_child_flushes_at_the_ceiling_and_ends_on_the_result() { + let dir = tempfile::tempdir().unwrap(); + let lines = [at("m1", 10), at("m2", CEILING_TOKENS)].join(" +"); + let result = result_line(); + // Prints two requests, waits for the flush on stdin, then the result, as a stand-in for a Self that obeys. + let body = format!("cat <<'EOF' +{lines} +EOF +read -t 5 start +read -t 5 flush +cat <<'EOF' +{result} +EOF +while read more; do :; done +exit 0"); + let program = script(&dir, &body); + let mind = with_run(); + let mut out = Vec::new(); + let outcome = carry(&Seen::of(&mind), &SystemClock, &FakeLender::new(Answer::Grant), &Spawn(program), TEST_CAP_S, &mut out).unwrap(); + assert_eq!(outcome, SelfRunOutcome::Ended { cause: Cause::FlushRequested, code: Some(0), requests: 2, prefix: 300_000 }); + assert_eq!(String::from_utf8(out).unwrap(), "request 1 prefix 10 +request 2 prefix 300000 +result subtype=success is_error=true: Failed to authenticate: OAuth session expired and could not be refreshed +"); + } + #[test] fn a_failure_result_before_any_request_closes_stdin_and_ends_the_run_promptly_through_the_real_child() { let dir = tempfile::tempdir().unwrap(); diff --git a/crates/mind-body/tests/cli.rs b/crates/mind-body/tests/cli.rs index 8ae92c2..19661a0 100644 --- a/crates/mind-body/tests/cli.rs +++ b/crates/mind-body/tests/cli.rs @@ -69,7 +69,6 @@ fn malformed_commands_are_refused_and_write_nothing() { &["--instance", "eureka"], &["--instance", "eureka", "brake"], &["--instance", "eureka", "brake", "toggle"], - &["--instance", "eureka", "dial", "set", "--heat", "1"], &["--instance", "../evil", "brake", "release"], &["--instances", "eureka", "brake", "release"], &["eureka", "eureka", "brake", "release"], @@ -187,9 +186,10 @@ fn check_refuses_every_reading_but_released() { held(root.path(), "BrakeHeld"); go(root.path(), &["--instance", "eureka", "brake", "release"], "op", 2).0.unwrap(); held(root.path(), "BrakeAbsent"); - go(root.path(), &["swarm", "brake", "release"], "op", 3).0.unwrap(); - held(root.path(), "DialAbsent"); - // DialOutOfBounds cannot be written through the CLI: it is pinned in the crate's own tests. + go(root.path(), &["swarm", "brake", "hold"], "op", 3).0.unwrap(); + held(root.path(), "BrakeHeld"); + go(root.path(), &["swarm", "brake", "release"], "op", 4).0.unwrap(); + check_out(root.path()).0.unwrap(); // An undecodable store is a held reading too. let broken = tempfile::tempdir().unwrap(); std::fs::create_dir(broken.path().join("eureka")).unwrap(); @@ -215,7 +215,7 @@ fn check_holds_for_the_named_instance_with_a_released_prefix_neighbour() { let root = tempfile::tempdir().unwrap(); go(root.path(), &["--instance", "eureka", "brake", "hold"], "op", 0).0.unwrap(); go(root.path(), &["swarm", "brake", "release"], "op", 0).0.unwrap(); - // Neighbours that share the held instance's text as a prefix, or only an edge of it, are released with a dial. + // Neighbours that share the held instance's text as a prefix, or only an edge of it, are released. for (i, neighbour) in ["eureka-two", "eureka2", "xeureka"].into_iter().enumerate() { let at = 1 + 2 * i as i64; go(root.path(), &["--instance", neighbour, "brake", "release"], "op", at).0.unwrap(); @@ -281,7 +281,6 @@ fn only_brake_hold_calls_the_stopper() { &["--instance", "eureka", "show"], &["--instance", "eureka", "brake"], &["--instance", "eureka", "brake", "toggle"], - &["swarm", "dial", "set", "--heat", "99", "--base-rate", "4", "--base-run", "1", "--k", "2"], &["--instance", "../evil", "brake", "release"], &["--instance", "eureka", "brake", "hold", "now"], &[], @@ -470,14 +469,6 @@ fn swarm_commands_refuse_bad_input_and_write_nothing() { let root = tempfile::tempdir().unwrap(); for args in [ &["swarm"][..], - &["swarm", "dial", "set", "--heat", "1"], - &["swarm", "dial", "set", "--heat", "3", "--base-rate", "4", "--base-run", "2", "--k", "3"], - &["swarm", "dial", "set", "--heat", "1", "--base-rate", "4", "--base-run", "2", "--k", "0"], - &["swarm", "dial", "set", "--heat", "1", "--heat", "1", "--base-rate", "4", "--base-run", "2", "--k", "3"], - &["swarm", "share", "set", "weksa", "0"], - &["swarm", "share", "set", "weksa", "heavy"], - &["swarm", "share", "set", "Not A Slug", "1"], - &["swarm", "share", "set", "weksa"], &["swarm", "brake", "release", "now"], ] { assert!(go(root.path(), args, "op", 0).0.is_err(), "{args:?} was accepted"); diff --git a/crates/mind-body/tests/public_surface.txt b/crates/mind-body/tests/public_surface.txt index 1b7079c..087871e 100644 --- a/crates/mind-body/tests/public_surface.txt +++ b/crates/mind-body/tests/public_surface.txt @@ -9,6 +9,7 @@ mind_body: mod lease_client mind_body: mod persona mind_body: mod queue mind_body: mod self_run +mind_body: mod tempo mind_body::chat_client: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum Refusal { Brake , PersonaMismatch , UnknownRoom , EmptyText , SendFailed , UnknownPersona , } mind_body::chat_client: const ACK : & str mind_body::chat_client: const PENDING : & str @@ -43,22 +44,18 @@ mind_body::cli: fn run (args : & [String] , root : & Path , set_by : & str , now mind_body::connector: const MAX_OUTPUT_TOKENS : u32 mind_body::connector: fn endpoint (text : & str) -> Result < SocketAddr > mind_body::connector: fn key (key_file_text : & str) -> String +mind_body::connector: fn render_plan (windows : & [PlanQuota] , now_ms : u64) -> String mind_body::connector: impl Connector for KitConnector mind_body::connector: impl KitConnector mind_body::connector: impl KitConnector: fn new (endpoint : SocketAddr , key_text : & str , model : impl Into < String > , caller : impl Into < String >) -> Result < Self > mind_body::connector: struct KitConnector pub-fields[] -mind_body::control: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum HeldReason { BrakeAbsent , BrakeHeld , DialAbsent , DialOutOfBounds , HeatZero , ShareOutOfBounds , Undecodable , } +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 (Envelope) , } +mind_body::control: # [derive (Debug , PartialEq , Eq)] pub enum SwarmEffective { Held (HeldReason) , Released , } mind_body::control: const BRAKE_KEY : & str mind_body::control: const CONTROL_ROOT : & str -mind_body::control: const DIAL_KEY : & str -mind_body::control: const K_MAX : u32 mind_body::control: const SWARM_FILE : & str -mind_body::control: fn base_rate_max () -> Decimal -mind_body::control: fn base_run_max () -> Decimal mind_body::control: fn control_path (root : & Path , instance : & Slug) -> Result < PathBuf > -mind_body::control: fn heat_max () -> Decimal mind_body::control: fn load_state (source : & impl ControlSource) -> Result < ControlState > mind_body::control: fn load_swarm_state (source : & impl ControlSource) -> Result < SwarmState > mind_body::control: fn read_effective (instance : & Slug) -> Effective @@ -69,81 +66,54 @@ mind_body::control: impl ControlState mind_body::control: impl ControlWriter mind_body::control: impl ControlWriter: fn open (path : impl Into < PathBuf >) -> Result < Self > mind_body::control: impl ControlWriter: fn set_brake (& mut self , released : bool , set_at : DateTime < Utc > , set_by : & str) -> Result < () > -mind_body::control: impl Envelope -mind_body::control: impl Envelope: fn grant_for (& self , instance : & Slug , instance_effective : & Effective , budget_usd : Decimal) -> Option < Grant > -mind_body::control: impl Envelope: fn k (& self) -> u32 -mind_body::control: impl Envelope: fn rate_usd_per_h (& self) -> Decimal -mind_body::control: impl Envelope: fn run_cap_usd (& self) -> Decimal -mind_body::control: impl Envelope: fn share (& self , instance : & Slug) -> Decimal mind_body::control: impl FileSource mind_body::control: impl FileSource: fn new (path : impl Into < PathBuf >) -> Self mind_body::control: impl Grant mind_body::control: impl Grant: fn instance (& self) -> & Slug -mind_body::control: impl Grant: fn run_cap_usd (& self) -> Decimal -mind_body::control: impl Share -mind_body::control: impl Share: fn in_bounds (& self) -> bool -mind_body::control: impl SwarmDial -mind_body::control: impl SwarmDial: fn in_bounds (& self) -> bool mind_body::control: impl SwarmState mind_body::control: impl SwarmWriter mind_body::control: impl SwarmWriter: fn open (path : impl Into < PathBuf >) -> Result < Self > mind_body::control: impl SwarmWriter: fn set_brake (& mut self , released : bool , set_at : DateTime < Utc > , set_by : & str) -> Result < () > -mind_body::control: impl SwarmWriter: fn set_dial (& mut self , dial : SwarmDial) -> Result < () > -mind_body::control: impl SwarmWriter: fn set_share (& mut self , share : Share) -> Result < () > mind_body::control: impl std :: error :: Error for UnsafeStore mind_body::control: impl std :: fmt :: Display for UnsafeStore mind_body::control: pub trait ControlSource { fn snapshot (& self) -> Result < Vec < CultCacheEnvelope > > ; } mind_body::control: struct Brake # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[released: bool, set_at: DateTime < Utc >, set_by: String] mind_body::control: struct ControlState # [derive (Clone , Debug , PartialEq , Eq , Default)] pub-fields[brake: Option < Brake >] mind_body::control: struct ControlWriter pub-fields[] -mind_body::control: struct Envelope # [derive (Debug , PartialEq , Eq)] pub-fields[] mind_body::control: struct FileSource pub-fields[] mind_body::control: struct Grant # [derive (Debug , PartialEq , Eq)] pub-fields[] -mind_body::control: struct Share # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[instance: String, weight: Decimal] -mind_body::control: struct SwarmDial # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[heat: Decimal, base_rate_usd_per_h: Decimal, base_run_usd: Decimal, k: u32, set_at: DateTime < Utc >, set_by: String] -mind_body::control: struct SwarmState # [derive (Clone , Debug , PartialEq , Eq , Default)] pub-fields[brake: Option < Brake >, dial: Option < SwarmDial >, shares: Vec < Share >] +mind_body::control: struct SwarmState # [derive (Clone , Debug , PartialEq , Eq , Default)] pub-fields[brake: Option < Brake >] mind_body::control: struct SwarmWriter pub-fields[] mind_body::control: struct UnsafeStore # [derive (Debug)] pub-fields[path: PathBuf, mode: u32, uid: u32] mind_body::heartbeat: # [derive (Clone , Debug , PartialEq , Eq)] pub enum Outcome { Held (HeldReason) , Polled { launched : Vec < Slug > , } , } mind_body::heartbeat: const AGENT : & str -mind_body::heartbeat: const CLASSIFIER_CALLER : & str mind_body::heartbeat: fn poll (organs : & Organs , config : & Config) -> Result < Outcome > -mind_body::heartbeat: fn store_classifier_config (path : impl Into < PathBuf > , config : & ClassifierConfig) -> Result < () > -mind_body::heartbeat: impl ClassifierConfig -mind_body::heartbeat: impl ClassifierConfig: fn standard () -> Self -mind_body::heartbeat: impl ClassifierConfig: fn validate (& self) -> Result < () > mind_body::heartbeat: impl Config mind_body::heartbeat: impl Config: fn new (personas : Vec < PersonaId > , operators : Vec < String >) -> Result < Self > mind_body::heartbeat: impl Control for FileControl mind_body::heartbeat: impl FileStore +mind_body::heartbeat: impl FileStore: fn check (& self) -> Result < () > mind_body::heartbeat: impl FileStore: fn new (path : impl Into < PathBuf >) -> Self -mind_body::heartbeat: impl FileTunables -mind_body::heartbeat: impl FileTunables: fn new (path : impl Into < PathBuf >) -> Self -mind_body::heartbeat: impl Initiative -mind_body::heartbeat: impl Models for KitConnector +mind_body::heartbeat: impl MindRead mind_body::heartbeat: impl PersonaId mind_body::heartbeat: impl PersonaId: fn parse (spec : & str) -> Result < Self > +mind_body::heartbeat: impl Row < '_ > mind_body::heartbeat: impl Store for FileStore -mind_body::heartbeat: impl Tunables for FileTunables +mind_body::heartbeat: impl TempoView for FileStore mind_body::heartbeat: impl fmt :: Display for Outcome mind_body::heartbeat: impl fmt :: Display for PollTrace mind_body::heartbeat: pub trait Control { fn envelope (& self) -> SwarmEffective ; fn effective (& self , instance : & Slug) -> Effective ; } -mind_body::heartbeat: pub trait Models { fn classify (& self , request : & TurnRequest) -> Result < String > ; fn spent (& self , caller : & str) -> Result < Spend > ; } -mind_body::heartbeat: pub trait Store { fn load (& self , instance : & Slug) -> Result < Option < Initiative > > ; fn save (& self , instance : & Slug , initiative : & Initiative) -> Result < () > ; fn record (& self , trace : & PollTrace) -> Result < () > ; fn latest (& self) -> Result < Option < PollTrace > > ; } -mind_body::heartbeat: pub trait Tunables { fn classifier (& self) -> Result < ClassifierConfig > ; } -mind_body::heartbeat: struct BurnTrace # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[run: String, spend_usd: Decimal, source: String] -mind_body::heartbeat: struct ClassifierConfig # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[instructions: String, show_idle: bool, show_urgency: bool, show_completions: bool, urgency_min: Decimal, urgency_max: Decimal, urgency_ttl_s: u32] +mind_body::heartbeat: pub trait Store { fn load (& self) -> Result < Option < HeartbeatState > > ; fn save (& self , state : & HeartbeatState) -> Result < () > ; fn record (& self , trace : & PollTrace) -> Result < () > ; } +mind_body::heartbeat: pub trait TempoView { fn latest (& self) -> Result < Option < PollTrace > > ; } mind_body::heartbeat: struct Config # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[personas: Vec < PersonaId >, operators: Vec < String >] mind_body::heartbeat: struct FileControl pub-fields[] mind_body::heartbeat: struct FileStore pub-fields[] -mind_body::heartbeat: struct FileTunables pub-fields[] -mind_body::heartbeat: struct Initiative # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[initiative_usd: Decimal, as_of: DateTime < Utc >, urgency: Decimal, urgency_at: DateTime < Utc >, last_burned_run: String, filled_events: Vec < String >, classified_seq: String, launched_run: String, launch_spend_usd: Decimal, launch_spend_known: bool, completions_through: String, launch_window: u64] -mind_body::heartbeat: struct InstanceTrace # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[instance: String, initiative_usd: Decimal, rate_usd_per_h: Decimal, urgency: Decimal, urgency_age_s: i64, busy: bool, released: bool, crossing_at: String, waited_s: i64, idle_s: i64, filled_by: String, fill_reason: String, note: String] -mind_body::heartbeat: struct Organs< 'a > pub-fields[seats: & 'a [Seat < 'a >], control: & 'a dyn Control, models: & 'a dyn Models, tunables: & 'a dyn Tunables, launcher: & 'a dyn Launcher, clock: & 'a dyn Clock, host: & 'a str, store: & 'a dyn Store] +mind_body::heartbeat: struct HeartbeatState # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[last_beat_at: DateTime < Utc >, handled_mentions: Vec < String >] +mind_body::heartbeat: struct InstanceTrace # [derive (Clone , Debug , PartialEq , Eq , serde :: Serialize , serde :: Deserialize)] pub-fields[instance: String, vote_beat_s: Option < u32 >, urgency_permille: u16, addressed: bool, busy: bool, released: bool, idle_s: i64, score: String, note: String] +mind_body::heartbeat: struct Organs< 'a > pub-fields[seats: & 'a [Seat < 'a >], control: & 'a dyn Control, launcher: & 'a dyn Launcher, clock: & 'a dyn Clock, host: & 'a str, store: & 'a dyn Store] mind_body::heartbeat: struct PersonaId # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[slug: Slug, public_name: String, handle: String, matrix_id: String, huginn: SocketAddr] -mind_body::heartbeat: struct PollTrace # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[at: DateTime < Utc >, instances: Vec < InstanceTrace >, launches: Vec < String >, burns: Vec < BurnTrace >, classifier: String] +mind_body::heartbeat: struct PollTrace # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[at: DateTime < Utc >, tempo_s: u32, instances: Vec < InstanceTrace >, launches: Vec < String >] mind_body::heartbeat: struct Seat< 'a > pub-fields[who: PersonaId, mind: & 'a dyn MindPort, chat: & 'a dyn Chat] -mind_body::heartbeat: struct Spend # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub-fields[usd: Decimal, window: u64] mind_body::launch: # [derive (Clone , Debug , PartialEq , Eq)] pub enum Declined { Admission (MindRefusal) , Conflict , AlreadyAdmitted , StartFailed , GrantForOtherInstance , PersonaRunTakesNoClaims , } mind_body::launch: # [derive (Clone , Debug , PartialEq , Eq)] pub enum LaunchOutcome { Launched { run : PipelineRef , } , NotQueued { item : PipelineRef , } , Tripped { item : PipelineRef , question : PipelineRef , } , Busy { run : PipelineRef , } , Refused (Declined) , } mind_body::launch: const START_GRACE_S : i64 @@ -164,7 +134,7 @@ mind_body::launch: struct Ports< 'a > pub-fields[mind: & 'a dyn MindPort, launc mind_body::launch: struct SystemClock pub-fields[] mind_body::launch: struct SystemctlReply pub-fields[success: bool, code: Option < i32 >, stdout: String] mind_body::launch: struct SystemdLauncher pub-fields[] -mind_body::lease_client: # [derive (Clone , Copy , Debug , PartialEq , Eq , Serialize , Deserialize)] pub enum LeaseRefusalKind { SpendCeiling , CostBound , UnknownCaller , Expired , Busy , } +mind_body::lease_client: # [derive (Clone , Copy , Debug , PartialEq , Eq , Serialize , Deserialize)] pub enum LeaseRefusalKind { UnknownCaller , Expired , Busy , ProviderRejected , } mind_body::lease_client: const LEASE_SOCKET_VARIABLE : & str mind_body::lease_client: const MAX_LEASE_FRAME_BYTES : usize mind_body::lease_client: impl Inheritable @@ -176,28 +146,23 @@ mind_body::lease_client: impl TokenFd mind_body::lease_client: impl TokenFd: fn inheritable (& self) -> Result < Inheritable > mind_body::lease_client: impl std :: fmt :: Debug for TokenFd mind_body::lease_client: pub enum Lent { Granted (Box < dyn Lease >) , Refused (LeaseRefused) , } -mind_body::lease_client: pub trait Lease { fn token (& self) -> & TokenFd ; fn settle (self : Box < Self > , settlement : Settlement) -> Result < u64 > ; } +mind_body::lease_client: pub trait Lease { fn token (& self) -> & TokenFd ; fn plan_quota (& self) -> & [PlanQuota] ; } mind_body::lease_client: pub trait Lender { fn lend (& self , request : & LeaseRequest) -> Result < Lent > ; } mind_body::lease_client: struct Inheritable pub-fields[] mind_body::lease_client: struct LeaseRefused # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[kind: LeaseRefusalKind, detail: String] mind_body::lease_client: struct LeaseRequest # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[expires_at_unix_ms: u64] -mind_body::lease_client: struct ModelUsage # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[model: String, usage: TokenUsage] -mind_body::lease_client: struct Settlement # [derive (Clone , Debug , Default , PartialEq , Eq)] pub-fields[usage: Vec < ModelUsage >] mind_body::lease_client: struct SocketLender pub-fields[] mind_body::lease_client: struct TokenFd pub-fields[] -mind_body::lease_client: struct TokenUsage # [derive (Clone , Debug , Default , PartialEq , Eq , Serialize , Deserialize)] pub-fields[input_tokens: u64, cache_read_tokens: u64, cache_write_tokens: u64, output_tokens: u64, reasoning_tokens: Option < u64 >] mind_body::lease_client::socket: impl Lease for Granted -mind_body::persona: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum Failure { Held (HeldReason) , Chat , Persona , Doctrine , DoctrineTooLarge , Readout , Connector , Undecodable , RoomNotAllowed , Send , SelfRun , Ack , } +mind_body::persona: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum Failure { Held (HeldReason) , Chat , Persona , Doctrine , DoctrineTooLarge , Connector , Undecodable , RoomNotAllowed , Send , SelfRun , Pulse , Ack , } mind_body::persona: # [derive (Debug , PartialEq , Eq)] pub enum Outcome { Dead (HeldReason) , NoRun , Served { run : PipelineRef , answered : u32 , failure : Option < Failure > , self_run : Option < PipelineRef > } , } mind_body::persona: const AGENT : & str mind_body::persona: const DOCTRINE_MAX_BYTES : usize -mind_body::persona: const ENV_BURN_ROOM : & str mind_body::persona: const ENV_DOCTRINE : & str mind_body::persona: const ENV_OPERATOR : & str mind_body::persona: const ENV_ROOMS : & str mind_body::persona: const ENV_SELF_SENDER : & str mind_body::persona: const MAX_TURNS_PER_START : u32 -mind_body::persona: fn burn_line (readout : & SpendReadout , caller : & str , heat : Option < Decimal > , live_runs : usize) -> Option < String > mind_body::persona: fn reply_schema (rooms : & [String] , campaigns : & [String]) -> String mind_body::persona: fn start (organs : & Organs , control : & impl ControlSource , swarm : & impl ControlSource , config : & Config) -> Result < Outcome > mind_body::persona: impl Config @@ -213,19 +178,19 @@ mind_body::persona: impl Trace for FileTrace mind_body::persona: impl Trace for Noted < '_ > mind_body::persona: impl fmt :: Display for Failure mind_body::persona: impl fmt :: Display for Outcome -mind_body::persona: pub trait Connector { fn turn (& self , request : & TurnRequest) -> Result < String > ; fn spend_readout (& self) -> Result < SpendReadout > ; } +mind_body::persona: pub trait Connector { fn turn (& self , request : & TurnRequest) -> Result < String > ; fn usage_readout (& self) -> Result < UsageReadout > ; } mind_body::persona: pub trait PersonaState { fn instructions (& self) -> Result < String > ; } mind_body::persona: pub trait Trace { fn record (& self , trace : & TurnTrace) -> Result < () > ; } -mind_body::persona: pub use model_connector :: { CallerSpend , PlanQuota , SpendReadout } ; -mind_body::persona: struct Config # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[rooms: Vec < String >, burn_room: String, operators: Vec < String >, own_sender: String, max_turns: u32, doctrine: Vec < PathBuf >] +mind_body::persona: pub use model_connector :: { PlanQuota , UsageReadout } ; +mind_body::persona: struct Config # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[rooms: Vec < String >, operators: Vec < String >, own_sender: String, max_turns: u32, doctrine: Vec < PathBuf >] mind_body::persona: struct FileTrace pub-fields[] mind_body::persona: struct HuginnPersona pub-fields[] -mind_body::persona: struct Organs< 'a > pub-fields[mind: & 'a dyn MindPort, launcher: & 'a dyn Launcher, clock: & 'a dyn Clock, host: & 'a str, chat: & 'a dyn Chat, connector: & 'a dyn Connector, persona: & 'a dyn PersonaState, trace: & 'a dyn Trace] -mind_body::persona: struct PersonaReply # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[say: Vec < Say >, start_self_run: Option < StartSelfRun >] +mind_body::persona: struct Organs< 'a > pub-fields[mind: & 'a dyn MindPort, launcher: & 'a dyn Launcher, clock: & 'a dyn Clock, host: & 'a str, chat: & 'a dyn Chat, connector: & 'a dyn Connector, persona: & 'a dyn PersonaState, trace: & 'a dyn Trace, tempo: Option < & 'a dyn TempoView >] +mind_body::persona: struct PersonaReply # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[say: Vec < Say >, start_self_run: Option < StartSelfRun >, tempo_vote_beat_s: Option < u32 >, urgency_permille: Option < u16 >] mind_body::persona: struct Say # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[room: String, text: String] mind_body::persona: struct StartSelfRun # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[brief: String, repo: String, campaign: String] mind_body::persona: struct TraceItem # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[provenance: String, room: String, text: String] -mind_body::persona: struct TurnRequest # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[caller: String, request_id: String, max_cost_micro_usd: u64, instructions: String, input: String, output_schema_json: String] +mind_body::persona: struct TurnRequest # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[caller: String, request_id: String, instructions: String, input: String, output_schema_json: String] mind_body::persona: struct TurnTrace # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[run: String, turn: u32, at: DateTime < Utc >, inputs: Vec < TraceItem >, outputs: Vec < TraceItem >, outcome: String] mind_body::queue: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum Breaker { Clear , Trip , } mind_body::queue: const CONSECUTIVE : usize @@ -257,23 +222,30 @@ mind_body::self_run: const SCRATCH_VARIABLE : & str mind_body::self_run: const SEED_VARIABLE : & str mind_body::self_run: const SESSION_NAMESPACE : Uuid mind_body::self_run: fn carry (mind : & dyn MindPort , clock : & dyn Clock , lender : & dyn Lender , spawner : & dyn Spawner , run_cap_s : u64 , out : & mut dyn Write) -> Result < SelfRunOutcome > -mind_body::self_run: fn claude_args (session : Uuid , budget_usd : & str) -> Vec < String > +mind_body::self_run: fn claude_args (session : Uuid) -> Vec < String > mind_body::self_run: fn read_usage (line : & str) -> Usage mind_body::self_run: fn session_id (run : & PipelineRef) -> Uuid -mind_body::self_run: fn start_message (instance : & Slug , run : & PipelineRef) -> String +mind_body::self_run: fn start_message (instance : & Slug , run : & PipelineRef , plan_quota : & [PlanQuota] , now_ms : u64) -> String mind_body::self_run: impl Child for ClaudeProcess mind_body::self_run: impl ClaudeSpawner mind_body::self_run: impl ClaudeSpawner: fn new (unit_environment : impl IntoIterator < Item = (OsString , OsString) >) -> Result < Self > mind_body::self_run: impl Drop for FreshConfig mind_body::self_run: impl FreshConfig +mind_body::self_run: impl Heard mind_body::self_run: impl SelfRunOutcome mind_body::self_run: impl SelfRunOutcome: fn exit_status (& self) -> i32 mind_body::self_run: impl Spawner for ClaudeSpawner -mind_body::self_run: impl Spent mind_body::self_run: impl Stop mind_body::self_run: impl fmt :: Display for SeedLinkRefused -mind_body::self_run: impl std :: error :: Error for SeedLinkRefused mind_body::self_run: impl fmt :: Display for SelfRunOutcome +mind_body::self_run: impl std :: error :: Error for SeedLinkRefused mind_body::self_run: pub trait Child { fn write_line (& mut self , line : & str) ; fn close_input (& mut self) ; fn next (& mut self , timeout : StdDuration) -> Read ; fn stop (& mut self , how : Stop) -> bool ; } mind_body::self_run: pub trait Spawner { fn spawn (& self , args : & [String] , token : & TokenFd) -> Result < Box < dyn Child > > ; } mind_body::self_run: struct ClaudeSpawner pub-fields[] +mind_body::tempo: const DEFAULT_BEAT_S : u32 +mind_body::tempo: const LOG_BOUND_ORDERS : f64 +mind_body::tempo: fn pick (rows : & [Standing] , beat_s : u32) -> Option < usize > +mind_body::tempo: fn score (standing : & Standing , beat_s : u32) -> f64 +mind_body::tempo: fn tally (votes : & [u32]) -> u32 +mind_body::tempo: fn urgency_permille (standing : & Standing) -> u16 +mind_body::tempo: struct Standing # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[slug: String, eligible: bool, waited_s: Option < u64 >, last_turn_ms: Option < i64 >, self_permille: u16, topic_permille: u16, addressed_by_member: bool]