public_10_full_learning_loop_smoke/
10_full_learning_loop_smoke.rs1#[path = "../support/utils.rs"]
2mod _utils;
3
4use _utils::{boxed_error, cleanup_run, create_client, new_run_id, print_summary, require};
5use mubit_sdk::{
6 ArchiveOptions, CheckpointOptions, DereferenceOptions, DiagnoseOptions, FeedbackOptions,
7 GetContextOptions, HandoffOptions, ListAgentsOptions, MemoryHealthOptions, RecallOptions,
8 RecordOutcomeOptions, ReflectOptions, RegisterAgentOptions, RememberOptions,
9 SurfaceStrategiesOptions, TransportMode,
10};
11use serde_json::{json, Value};
12use std::error::Error;
13use std::time::Instant;
14
15#[tokio::main(flavor = "current_thread")]
16async fn main() -> Result<(), Box<dyn Error>> {
17 let name = "public_10_full_learning_loop_smoke";
18 let started = Instant::now();
19 let client = create_client().await?;
20 let run_id = new_run_id("public_10_full_learning_loop_smoke");
21 client.set_run_id(Some(run_id.clone()));
22 client.set_transport(TransportMode::Http);
23
24 let mut passed = true;
25 let mut detail = "validated all helper flows".to_string();
26 let mut metrics = json!({});
27
28 let scenario = async {
29 let mut remember = RememberOptions::new("Policy limit is 5000 and claimant city is Leeds.");
30 remember.run_id = Some(run_id.clone());
31 remember.agent_id = Some("planner".to_string());
32 remember.intent = Some("fact".to_string());
33 remember.metadata = Some(json!({"suite": "all-helpers", "family": "claims"}));
34 remember.importance = Some("high".to_string());
35 remember.occurrence_time = Some(std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64 - 86400);
36 let remember_response = client.remember(remember).await?;
37
38 let mut lesson = RememberOptions::new("If city is missing from the latest step, check stored claim facts before escalating.");
39 lesson.run_id = Some(run_id.clone());
40 lesson.agent_id = Some("planner".to_string());
41 lesson.intent = Some("lesson".to_string());
42 lesson.lesson_type = Some("success".to_string());
43 lesson.lesson_scope = Some("session".to_string());
44 lesson.lesson_importance = Some("high".to_string());
45 let lesson_response = client.remember(lesson).await?;
46
47 let mut mental_model = RememberOptions::new(
48 "Claims case overview: Policy limit 5000 GBP, claimant city Leeds, \
49 missing-city branch resolved via stored claim facts. Lesson: check stored facts before escalating.",
50 );
51 mental_model.run_id = Some(run_id.clone());
52 mental_model.agent_id = Some("planner".to_string());
53 mental_model.intent = Some("mental_model".to_string());
54 mental_model.importance = Some("critical".to_string());
55 mental_model.metadata = Some(json!({"suite": "all-helpers", "entity": "claims-case", "consolidated": true}));
56 let mental_model_response = client.remember(mental_model).await?;
57
58 let mut recall = RecallOptions::new("What is the policy limit?");
59 recall.run_id = Some(run_id.clone());
60 recall.agent_id = Some("planner".to_string());
61 recall.limit = 5;
62 let recall_response = client.recall(recall).await?;
63
64 let mut context = GetContextOptions::default();
65 context.run_id = Some(run_id.clone());
66 context.query = Some("Prepare a short adjuster summary".to_string());
67 context.agent_id = Some("planner".to_string());
68 context.limit = Some(6);
69 context.max_token_budget = Some(700);
70 let context_response = client.get_context(context).await?;
71
72 let mut health = MemoryHealthOptions::default();
73 health.run_id = Some(run_id.clone());
74 health.limit = 50;
75 let health_response = client.memory_health(health).await?;
76
77 let mut diagnose = DiagnoseOptions::new("policy limit lookup returned empty");
78 diagnose.run_id = Some(run_id.clone());
79 diagnose.error_type = Some("retrieval".to_string());
80 diagnose.limit = 5;
81 let diagnose_response = client.diagnose(diagnose).await?;
82
83 let mut reflect = ReflectOptions::default();
84 reflect.run_id = Some(run_id.clone());
85 let reflect_response = client.reflect(reflect).await?;
86
87 let mut register = RegisterAgentOptions::new("planner");
88 register.run_id = Some(run_id.clone());
89 register.role = "planner".to_string();
90 register.read_scopes = vec!["fact".into(), "lesson".into(), "rule".into(), "mental_model".into(), "archive_block".into(), "handoff".into(), "feedback".into()];
91 register.write_scopes = vec!["fact".into(), "trace".into(), "lesson".into(), "observation".into(), "archive_block".into()];
92 register.shared_memory_lanes = vec!["knowledge".into(), "history".into()];
93 let register_response = client.register_agent(register).await?;
94
95 let mut list_agents = ListAgentsOptions::default();
96 list_agents.run_id = Some(run_id.clone());
97 let agents_response = client.list_agents(list_agents).await?;
98
99 let mut archive = ArchiveOptions::new(
100 "Exact adjuster note: claimant confirmed city is Leeds during triage.",
101 "adjuster_note",
102 );
103 archive.run_id = Some(run_id.clone());
104 archive.agent_id = Some("planner".to_string());
105 archive.origin_agent_id = Some("planner".to_string());
106 archive.source_attempt_id = Some("attempt-1".to_string());
107 archive.source_tool = Some("phone_call".to_string());
108 archive.labels = vec!["claims".to_string(), "triage".to_string()];
109 archive.family = Some("claims-adjustment".to_string());
110 archive.metadata = Some(json!({"case": "all-helpers"}));
111 let archive_response = client.archive(archive).await?;
112 let reference_id = archive_response
113 .get("reference_id")
114 .and_then(Value::as_str)
115 .ok_or_else(|| boxed_error(format!("archive reference missing: {archive_response}")))?
116 .to_string();
117
118 let mut dereference = DereferenceOptions::new(reference_id.clone());
119 dereference.run_id = Some(run_id.clone());
120 dereference.agent_id = Some("planner".to_string());
121 let dereference_response = client.dereference(dereference).await?;
122
123 let mut checkpoint = CheckpointOptions::new("Planner captured claim facts before window compaction.");
124 checkpoint.run_id = Some(run_id.clone());
125 checkpoint.label = Some("pre-compaction".to_string());
126 checkpoint.metadata = Some(json!({"stage": "analysis"}));
127 checkpoint.agent_id = Some("planner".to_string());
128 let checkpoint_response = client.checkpoint(checkpoint).await?;
129
130 let lessons_response = client.control.lessons(json!({"run_id": run_id, "limit": 20})).await?;
131 let lesson_id = lessons_response
132 .get("lessons")
133 .and_then(Value::as_array)
134 .and_then(|items| items.iter().find_map(|item| item.get("id").and_then(Value::as_str)))
135 .ok_or_else(|| boxed_error(format!("expected lesson id for outcome flow: {lessons_response}")))?
136 .to_string();
137
138 let mut outcome = RecordOutcomeOptions::new(lesson_id, "success");
139 outcome.run_id = Some(run_id.clone());
140 outcome.signal = 0.75;
141 outcome.rationale = "Planner reused stored claim fact and resolved the missing-city branch.".to_string();
142 outcome.agent_id = Some("planner".to_string());
143 let outcome_response = client.record_outcome(outcome).await?;
144
145 let mut strategies = SurfaceStrategiesOptions::default();
146 strategies.run_id = Some(run_id.clone());
147 strategies.lesson_types = vec!["success".to_string(), "rule".to_string()];
148 strategies.max_strategies = 5;
149 let strategies_response = client.surface_strategies(strategies).await?;
150
151 let mut handoff = HandoffOptions::new(
152 "claim-1",
153 "planner",
154 "reviewer",
155 "Review whether the stored claimant city is sufficient for routing.",
156 );
157 handoff.run_id = Some(run_id.clone());
158 handoff.requested_action = "review".to_string();
159 handoff.metadata = Some(json!({"team": "claims"}));
160 let handoff_response = client.handoff(handoff).await?;
161 let handoff_id = handoff_response
162 .get("handoff_id")
163 .and_then(Value::as_str)
164 .ok_or_else(|| boxed_error(format!("handoff id missing: {handoff_response}")))?
165 .to_string();
166
167 let mut feedback = FeedbackOptions::new(handoff_id.clone(), "approve");
168 feedback.run_id = Some(run_id.clone());
169 feedback.comments = "Stored fact is sufficient; proceed without escalation.".to_string();
170 feedback.from_agent_id = Some("reviewer".to_string());
171 feedback.metadata = Some(json!({"team": "claims"}));
172 let feedback_response = client.feedback(feedback).await?;
173
174 require(remember_response.get("job_id").is_some() || remember_response.get("accepted").and_then(Value::as_bool).unwrap_or(false), format!("remember failed: {remember_response}"))?;
175 require(mental_model_response.get("job_id").is_some() || mental_model_response.get("accepted").and_then(Value::as_bool).unwrap_or(false), format!("mental_model remember failed: {mental_model_response}"))?;
176 require(lesson_response.get("job_id").is_some() || lesson_response.get("accepted").and_then(Value::as_bool).unwrap_or(false), format!("lesson remember failed: {lesson_response}"))?;
177 require(recall_response.get("final_answer").is_some() || recall_response.get("evidence").is_some(), format!("recall failed: {recall_response}"))?;
178 require(context_response.get("sources").and_then(Value::as_array).is_some(), format!("context malformed: {context_response}"))?;
179 require(health_response.get("entry_counts").and_then(Value::as_object).is_some(), format!("memory_health malformed: {health_response}"))?;
180 require(diagnose_response.get("failure_lessons").and_then(Value::as_array).is_some(), format!("diagnose malformed: {diagnose_response}"))?;
181 require(reflect_response.get("lessons").and_then(Value::as_array).is_some(), format!("reflect malformed: {reflect_response}"))?;
182 require(register_response.get("success").and_then(Value::as_bool).unwrap_or(false), format!("register_agent failed: {register_response}"))?;
183 require(agents_response.get("agents").and_then(Value::as_array).map(|items| !items.is_empty()).unwrap_or(false), format!("list_agents failed: {agents_response}"))?;
184 require(archive_response.get("success").and_then(Value::as_bool).unwrap_or(false), format!("archive failed: {archive_response}"))?;
185 require(dereference_response.get("found").and_then(Value::as_bool).unwrap_or(false), format!("dereference failed: {dereference_response}"))?;
186 require(checkpoint_response.get("success").and_then(Value::as_bool).unwrap_or(false), format!("checkpoint failed: {checkpoint_response}"))?;
187 require(outcome_response.get("success").and_then(Value::as_bool).unwrap_or(false), format!("record_outcome failed: {outcome_response}"))?;
188 require(strategies_response.get("strategies").and_then(Value::as_array).is_some(), format!("surface_strategies malformed: {strategies_response}"))?;
189 require(handoff_response.get("success").and_then(Value::as_bool).unwrap_or(false), format!("handoff failed: {handoff_response}"))?;
190 require(feedback_response.get("success").and_then(Value::as_bool).unwrap_or(false), format!("feedback failed: {feedback_response}"))?;
191
192 let strategy_count = strategies_response
193 .get("strategies")
194 .and_then(Value::as_array)
195 .map(|items| items.len())
196 .unwrap_or(0);
197 let agent_count = agents_response
198 .get("agents")
199 .and_then(Value::as_array)
200 .map(|items| items.len())
201 .unwrap_or(0);
202 let lesson_count = lessons_response
203 .get("lessons")
204 .and_then(Value::as_array)
205 .map(|items| items.len())
206 .unwrap_or(0);
207
208 metrics = json!({
209 "run_id": run_id,
210 "agent_count": agent_count,
211 "strategy_count": strategy_count,
212 "reference_id": reference_id,
213 "lesson_count": lesson_count,
214 });
215
216 Ok::<(), Box<dyn Error>>(())
217 }
218 .await;
219
220 if let Err(err) = scenario {
221 passed = false;
222 detail = err.to_string();
223 }
224
225 let cleanup_ok = cleanup_run(&client, &run_id).await;
226 if !cleanup_ok {
227 passed = false;
228 detail = format!("{detail} | cleanup failures");
229 }
230
231 print_summary(name, passed, &detail, &metrics, started.elapsed().as_secs_f64(), cleanup_ok);
232
233 if passed { Ok(()) } else { Err(boxed_error(detail)) }
234}