pub trait DecisionCore<S: State, A: Action> {
type Outcome: Outcome;
// Required methods
fn domain(&self) -> DecisionDomain;
fn posterior(&self, evidence: &[EvidenceTerm]) -> Posterior<S>;
fn loss(&self, action: &A, state: &S) -> f64;
fn decide(&mut self, evidence: &[EvidenceTerm]) -> Decision<A>;
fn calibrate(&mut self, outcome: &Self::Outcome);
fn fallback_action(&self) -> A;
fn actions(&self) -> Vec<A>;
// Provided method
fn decide_and_record(
&mut self,
evidence: &[EvidenceTerm],
ledger: &mut UnifiedEvidenceLedger,
timestamp_ns: u64,
) -> Decision<A> { ... }
}Expand description
The universal decision-making trait.
Every adaptive controller in FrankenTUI implements this trait. The trait is generic over:
S: the state space (what the controller believes about the world)A: the action space (what the controller can choose to do)
§Contract
posterior()is pure: it does not mutate the controller.decide()may update internal counters (decision_id, timestamps) but must be deterministic given the same evidence and internal state.calibrate()updates the posterior with an observed outcome.fallback_action()must always succeed without allocation.
§Example
impl DecisionCore<ChangeRate, DiffAction> for DiffStrategyController {
fn domain(&self) -> DecisionDomain {
DecisionDomain::DiffStrategy
}
fn posterior(&self, evidence: &[EvidenceTerm]) -> Posterior<ChangeRate> {
// Beta-Bernoulli posterior on change rate
// ...
}
fn loss(&self, action: &DiffAction, state: &ChangeRate) -> f64 {
match action {
DiffAction::Full => state.full_cost(),
DiffAction::DirtyRows => state.dirty_cost(),
}
}
fn decide(&mut self, evidence: &[EvidenceTerm]) -> Decision<DiffAction> {
// Expected-loss minimization
// ...
}
fn calibrate(&mut self, outcome: &bool) {
// Update Beta posterior with observed match/mismatch
}
fn fallback_action(&self) -> DiffAction {
DiffAction::Full // safe default: full redraw
}
}Required Associated Types§
Required Methods§
Sourcefn domain(&self) -> DecisionDomain
fn domain(&self) -> DecisionDomain
Which evidence domain this controller belongs to.
Sourcefn posterior(&self, evidence: &[EvidenceTerm]) -> Posterior<S>
fn posterior(&self, evidence: &[EvidenceTerm]) -> Posterior<S>
Compute the posterior belief given current evidence.
The evidence terms are additional observations beyond what the
controller has already internalized via calibrate().
Sourcefn loss(&self, action: &A, state: &S) -> f64
fn loss(&self, action: &A, state: &S) -> f64
Compute the loss of taking action when the true state is state.
Lower loss = better action for this state.
Sourcefn decide(&mut self, evidence: &[EvidenceTerm]) -> Decision<A>
fn decide(&mut self, evidence: &[EvidenceTerm]) -> Decision<A>
Choose the optimal action by minimizing expected loss.
This is the main entry point. It:
- Computes the posterior from current evidence.
- Evaluates expected loss for each available action.
- Returns the action with minimum expected loss, plus full evidence.
Implementations may update internal state (decision counters, etc.).
Sourcefn calibrate(&mut self, outcome: &Self::Outcome)
fn calibrate(&mut self, outcome: &Self::Outcome)
Update the model with an observed outcome.
Called after decide() to close the feedback loop. The outcome
type is domain-specific (e.g., bool for match/mismatch,
f64 for measured cost, etc.).
Sourcefn fallback_action(&self) -> A
fn fallback_action(&self) -> A
Safe fallback action when the posterior is degenerate or computation fails.
This must always succeed without allocation. Typically returns the most conservative action (e.g., full redraw, no coalescing).
Provided Methods§
Sourcefn decide_and_record(
&mut self,
evidence: &[EvidenceTerm],
ledger: &mut UnifiedEvidenceLedger,
timestamp_ns: u64,
) -> Decision<A>
fn decide_and_record( &mut self, evidence: &[EvidenceTerm], ledger: &mut UnifiedEvidenceLedger, timestamp_ns: u64, ) -> Decision<A>
Make a decision and record it in the evidence ledger.
Convenience method that wraps decide() + ledger recording.