#[non_exhaustive]pub struct Action<A> {
pub id: A,
pub base: Score,
pub allowed: bool,
pub considerations: Vec<Consideration>,
}Expand description
A candidate decision: the value returned if chosen, plus the considerations that score it.
Utility is the base weight multiplied by every consideration. Because they multiply, any near-zero consideration vetoes the action — all of them must be satisfied for a high utility.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.id: AThe value returned when this action is chosen.
base: ScoreThe base weight before considerations (defaults to Score::MAX).
allowed: boolWhether this action is permitted. A gated-off action (false) always has
zero utility, so it is never chosen by decide/decide_weighted. Set it
with gate. Defaults to true.
considerations: Vec<Consideration>The considerations multiplied together to form the utility.
Implementations§
Source§impl<A> Action<A>
impl<A> Action<A>
Sourcepub fn new(id: A) -> Action<A>
pub fn new(id: A) -> Action<A>
Creates an action with a neutral base weight and no considerations.
Examples found in repository?
9fn main() {
10 // Signals about the current request, each normalized to 0.0..=1.0.
11 let urgency = Score::from_ratio(80, 100);
12 let confidence = Score::from_ratio(40, 100); // how well we already understand it
13 let llm_available = true; // e.g. circuit breaker closed AND rate limiter has tokens
14
15 let mut brain = Reasoner::new();
16
17 // Answer from a template — strong when we are already confident.
18 brain.add(Action::new("answer_template").consider_labeled(
19 "confidence",
20 Curve::Linear,
21 confidence,
22 ));
23
24 // Escalate to the LLM — strong when urgent, but only if the LLM is available.
25 brain.add(
26 Action::new("call_llm")
27 .gate(llm_available) // constraint-aware: skipped entirely if the LLM is down/limited
28 .consider_labeled("urgency", Curve::Linear, urgency),
29 );
30
31 // An always-available, low-weight fallback so a decision still resolves.
32 brain.add(Action::new("defer").with_base(Score::from_ratio(1, 10)));
33
34 // Abstain if nothing clears the bar — the caller would escalate to a human.
35 let threshold = Score::from_ratio(5, 100);
36 match brain.decide_above(threshold) {
37 Some(decision) => {
38 println!(
39 "chose: {} (utility {}/10000)",
40 decision.id,
41 decision.utility.raw()
42 );
43 if let Some(why) = brain.explain() {
44 for c in why.contributions {
45 println!(
46 " {} : input {} -> {}",
47 c.label,
48 c.input.raw(),
49 c.output.raw()
50 );
51 }
52 }
53 }
54 None => println!("nothing good enough — escalate to a human"),
55 }
56}Sourcepub fn gate(self, allowed: bool) -> Action<A>
pub fn gate(self, allowed: bool) -> Action<A>
Gates the action on a caller-supplied condition (builder style). Calls
combine with AND, so .gate(a).gate(b) is allowed only when both hold.
This is how decisions become constraint-aware without any dependency: the
caller passes whatever it already knows — a deadline, a rate limiter, a
circuit breaker, business hours, a feature flag — as a bool. A gated-off
action has zero utility and is never chosen. Keep one ungated fallback
action so a decision still resolves when everything else is gated off.
Examples found in repository?
9fn main() {
10 // Signals about the current request, each normalized to 0.0..=1.0.
11 let urgency = Score::from_ratio(80, 100);
12 let confidence = Score::from_ratio(40, 100); // how well we already understand it
13 let llm_available = true; // e.g. circuit breaker closed AND rate limiter has tokens
14
15 let mut brain = Reasoner::new();
16
17 // Answer from a template — strong when we are already confident.
18 brain.add(Action::new("answer_template").consider_labeled(
19 "confidence",
20 Curve::Linear,
21 confidence,
22 ));
23
24 // Escalate to the LLM — strong when urgent, but only if the LLM is available.
25 brain.add(
26 Action::new("call_llm")
27 .gate(llm_available) // constraint-aware: skipped entirely if the LLM is down/limited
28 .consider_labeled("urgency", Curve::Linear, urgency),
29 );
30
31 // An always-available, low-weight fallback so a decision still resolves.
32 brain.add(Action::new("defer").with_base(Score::from_ratio(1, 10)));
33
34 // Abstain if nothing clears the bar — the caller would escalate to a human.
35 let threshold = Score::from_ratio(5, 100);
36 match brain.decide_above(threshold) {
37 Some(decision) => {
38 println!(
39 "chose: {} (utility {}/10000)",
40 decision.id,
41 decision.utility.raw()
42 );
43 if let Some(why) = brain.explain() {
44 for c in why.contributions {
45 println!(
46 " {} : input {} -> {}",
47 c.label,
48 c.input.raw(),
49 c.output.raw()
50 );
51 }
52 }
53 }
54 None => println!("nothing good enough — escalate to a human"),
55 }
56}Sourcepub fn with_base(self, base: Score) -> Action<A>
pub fn with_base(self, base: Score) -> Action<A>
Sets the base weight (builder style).
Examples found in repository?
9fn main() {
10 // Signals about the current request, each normalized to 0.0..=1.0.
11 let urgency = Score::from_ratio(80, 100);
12 let confidence = Score::from_ratio(40, 100); // how well we already understand it
13 let llm_available = true; // e.g. circuit breaker closed AND rate limiter has tokens
14
15 let mut brain = Reasoner::new();
16
17 // Answer from a template — strong when we are already confident.
18 brain.add(Action::new("answer_template").consider_labeled(
19 "confidence",
20 Curve::Linear,
21 confidence,
22 ));
23
24 // Escalate to the LLM — strong when urgent, but only if the LLM is available.
25 brain.add(
26 Action::new("call_llm")
27 .gate(llm_available) // constraint-aware: skipped entirely if the LLM is down/limited
28 .consider_labeled("urgency", Curve::Linear, urgency),
29 );
30
31 // An always-available, low-weight fallback so a decision still resolves.
32 brain.add(Action::new("defer").with_base(Score::from_ratio(1, 10)));
33
34 // Abstain if nothing clears the bar — the caller would escalate to a human.
35 let threshold = Score::from_ratio(5, 100);
36 match brain.decide_above(threshold) {
37 Some(decision) => {
38 println!(
39 "chose: {} (utility {}/10000)",
40 decision.id,
41 decision.utility.raw()
42 );
43 if let Some(why) = brain.explain() {
44 for c in why.contributions {
45 println!(
46 " {} : input {} -> {}",
47 c.label,
48 c.input.raw(),
49 c.output.raw()
50 );
51 }
52 }
53 }
54 None => println!("nothing good enough — escalate to a human"),
55 }
56}Sourcepub fn consider(self, curve: Curve, input: Score) -> Action<A>
pub fn consider(self, curve: Curve, input: Score) -> Action<A>
Adds a consideration (builder style).
Sourcepub fn consider_labeled(
self,
label: &'static str,
curve: Curve,
input: Score,
) -> Action<A>
pub fn consider_labeled( self, label: &'static str, curve: Curve, input: Score, ) -> Action<A>
Adds a labeled consideration (builder style); the label appears in
Reasoner::explain output.
Examples found in repository?
9fn main() {
10 // Signals about the current request, each normalized to 0.0..=1.0.
11 let urgency = Score::from_ratio(80, 100);
12 let confidence = Score::from_ratio(40, 100); // how well we already understand it
13 let llm_available = true; // e.g. circuit breaker closed AND rate limiter has tokens
14
15 let mut brain = Reasoner::new();
16
17 // Answer from a template — strong when we are already confident.
18 brain.add(Action::new("answer_template").consider_labeled(
19 "confidence",
20 Curve::Linear,
21 confidence,
22 ));
23
24 // Escalate to the LLM — strong when urgent, but only if the LLM is available.
25 brain.add(
26 Action::new("call_llm")
27 .gate(llm_available) // constraint-aware: skipped entirely if the LLM is down/limited
28 .consider_labeled("urgency", Curve::Linear, urgency),
29 );
30
31 // An always-available, low-weight fallback so a decision still resolves.
32 brain.add(Action::new("defer").with_base(Score::from_ratio(1, 10)));
33
34 // Abstain if nothing clears the bar — the caller would escalate to a human.
35 let threshold = Score::from_ratio(5, 100);
36 match brain.decide_above(threshold) {
37 Some(decision) => {
38 println!(
39 "chose: {} (utility {}/10000)",
40 decision.id,
41 decision.utility.raw()
42 );
43 if let Some(why) = brain.explain() {
44 for c in why.contributions {
45 println!(
46 " {} : input {} -> {}",
47 c.label,
48 c.input.raw(),
49 c.output.raw()
50 );
51 }
52 }
53 }
54 None => println!("nothing good enough — escalate to a human"),
55 }
56}Sourcepub fn utility(&self) -> Score
pub fn utility(&self) -> Score
Computes the action’s utility: base * product(considerations), or
Score::ZERO if the action is gated off (allowed
is false).