Skip to main content

areev_loop_adapter/
decide.rs

1//! The bridge from Areev's decision seam to the loop's
2//! (`docs/decision-model-proposal.md` §4, rows E1–E3).
3//!
4//! `areev_loop` has no Areev dependencies, so it defines its own minimal
5//! [`areev_loop::DecideBackend`] over the proposal's WIRE JSON. This adapter
6//! implements it over any [`areev_core::decide::DecisionBackend`] — a
7//! provider chain from `areev_llm::decide::resolve_chain`, a command backend,
8//! or a test fake — so a host installs its chain with
9//! `engine.with_decider(Box::new(LoopDecider(chain)))`.
10
11use areev_core::decide::{questions_from_wire, DecideRequest, DecisionBackend};
12use std::sync::Arc;
13
14/// An Areev decision backend, seen by the loop.
15pub struct LoopDecider(pub Arc<dyn DecisionBackend>);
16
17impl areev_loop::DecideBackend for LoopDecider {
18    /// `{"state", "questions"}` in → the backend's validated `Decision` as
19    /// `{model, answers, usage?, provider, calibrated, latency_ms}` out.
20    /// Every failure — a request that does not parse, an invalid question,
21    /// any `DEC-E…` from the backend — is `LOP-E051`, which the loop treats
22    /// as "no contribution" for that stage.
23    fn decide(&self, request_json: &str) -> areev_loop::Result<String> {
24        let err = |m: String| areev_loop::Error::DecideBackend(m);
25        let v: serde_json::Value =
26            serde_json::from_str(request_json).map_err(|e| err(format!("request is not JSON: {e}")))?;
27        let questions = questions_from_wire(v.get("questions").unwrap_or(&serde_json::Value::Null))
28            .map_err(|e| err(e.to_string()))?;
29        let state = v.get("state").cloned().unwrap_or(serde_json::Value::Null);
30        let decision = self
31            .0
32            .decide(&DecideRequest::new(state, questions))
33            .map_err(|e| err(e.to_string()))?;
34        Ok(decision.to_json().to_string())
35    }
36
37    fn calibrated(&self) -> bool {
38        self.0.calibrated()
39    }
40
41    fn describe(&self) -> String {
42        self.0.describe()
43    }
44}