converge_pack/
pack_suggestor.rs1use async_trait::async_trait;
7
8use crate::Suggestor;
9use crate::context::{Context, ContextKey};
10use crate::effect::AgentEffect;
11use crate::fact::ProposedFact;
12use crate::gate::{ObjectiveSpec, ProblemSpec};
13use crate::pack::Pack;
14
15pub struct PackSuggestor<P: Pack> {
20 pack: P,
21 input_key: ContextKey,
22 output_key: ContextKey,
23}
24
25impl<P: Pack> PackSuggestor<P> {
26 pub fn new(pack: P, input_key: ContextKey, output_key: ContextKey) -> Self {
28 Self {
29 pack,
30 input_key,
31 output_key,
32 }
33 }
34}
35
36#[async_trait]
37impl<P: Pack> Suggestor for PackSuggestor<P> {
38 fn name(&self) -> &str {
39 self.pack.name()
40 }
41
42 fn dependencies(&self) -> &[ContextKey] {
43 std::slice::from_ref(&self.input_key)
44 }
45
46 fn accepts(&self, ctx: &dyn Context) -> bool {
47 ctx.has(self.input_key) && !ctx.has(self.output_key)
48 }
49
50 async fn execute(&self, ctx: &dyn Context) -> AgentEffect {
51 let facts = ctx.get(self.input_key);
52 let Some(seed_fact) = facts.first() else {
53 return AgentEffect::empty();
54 };
55
56 let inputs: serde_json::Value = match serde_json::from_str(&seed_fact.content) {
57 Ok(v) => v,
58 Err(_) => return AgentEffect::empty(),
59 };
60
61 let spec = match ProblemSpec::builder(format!("{}-converge", self.pack.name()), "converge")
62 .objective(ObjectiveSpec::maximize("default"))
63 .inputs_raw(inputs)
64 .build()
65 {
66 Ok(s) => s,
67 Err(_) => return AgentEffect::empty(),
68 };
69
70 match self.pack.solve(&spec) {
71 Ok(result) => {
72 let content = serde_json::to_string(&result.plan).unwrap_or_default();
73 let confidence = result.plan.confidence();
74 let proposal = ProposedFact::new(
75 self.output_key,
76 format!("{}-solution", self.pack.name()),
77 content,
78 format!("solver:{}", self.pack.name()),
79 )
80 .with_confidence(confidence);
81 AgentEffect::with_proposal(proposal)
82 }
83 Err(_) => AgentEffect::empty(),
84 }
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn pack_suggestor_constructed() {
94 struct DummyPack;
96 impl Pack for DummyPack {
97 fn name(&self) -> &'static str {
98 "dummy"
99 }
100 fn version(&self) -> &'static str {
101 "0.1.0"
102 }
103 fn validate_inputs(&self, _: &serde_json::Value) -> crate::gate::GateResult<()> {
104 Ok(())
105 }
106 fn invariants(&self) -> &[crate::pack::InvariantDef] {
107 &[]
108 }
109 fn solve(
110 &self,
111 _: &ProblemSpec,
112 ) -> crate::gate::GateResult<crate::pack::PackSolveResult> {
113 Err(crate::gate::GateError::invalid_input("not implemented"))
114 }
115 fn check_invariants(
116 &self,
117 _: &crate::gate::ProposedPlan,
118 ) -> crate::gate::GateResult<Vec<crate::pack::InvariantResult>> {
119 Ok(vec![])
120 }
121 fn evaluate_gate(
122 &self,
123 _: &crate::gate::ProposedPlan,
124 _: &[crate::pack::InvariantResult],
125 ) -> crate::gate::PromotionGate {
126 crate::gate::PromotionGate::auto_promote("ok")
127 }
128 }
129
130 let s = PackSuggestor::new(DummyPack, ContextKey::Seeds, ContextKey::Strategies);
131 assert_eq!(s.name(), "dummy");
132 assert_eq!(s.dependencies(), &[ContextKey::Seeds]);
133 }
134}