sim_lib_agent/agents/model/
runtime.rs1use super::super::tool_injection::{inject_manifest_tools, is_model_request};
2use super::super::{collect_agent_components, first_codec};
3use super::types::AgentState;
4use super::{
5 Agent, AgentFabric, AgentManifest, LoopbackStream, RuntimeValueSite, connection_from_value,
6 site_from_value, swarm_status_value_for_table,
7};
8use crate::{AgentComponent, BlackboardMemory, FileMemory, Tool, WorkingMemory, expr_to_value};
9use crate::{PersonaMemory, VectorMemory};
10use sim_kernel::{ClassRef, Cx, Error, EvalReply, Expr, Object, Result, Symbol, Value};
11use sim_lib_server::{
12 EvalSite, FrameKind, ServerAddress, ServerFrame, eval_reply_from_frame,
13 eval_request_from_frame, server_frame_from_reply,
14};
15use std::{
16 any::Any,
17 sync::{Arc, Mutex, OnceLock},
18};
19
20#[derive(Clone)]
21pub(crate) struct AgentEvalSite {
22 pub(crate) name: Symbol,
23 pub(crate) manifest: AgentManifest,
24 pub(crate) codecs: Vec<Symbol>,
25 pub(crate) capabilities: Vec<sim_kernel::CapabilityName>,
26}
27
28#[derive(Clone)]
29pub(crate) struct AgentDispatchSite {
30 pub(crate) state: Arc<Mutex<AgentState>>,
31 pub(crate) address: ServerAddress,
32 pub(crate) codecs: Vec<Symbol>,
33}
34
35impl EvalSite for AgentEvalSite {
36 fn site_kind(&self) -> &'static str {
37 "agent"
38 }
39
40 fn address(&self) -> &ServerAddress {
41 static LOCAL: OnceLock<ServerAddress> = OnceLock::new();
42 LOCAL.get_or_init(|| ServerAddress::Local)
43 }
44
45 fn codecs(&self) -> &[Symbol] {
46 &self.codecs
47 }
48
49 fn answer(&self, cx: &mut Cx, frame: ServerFrame) -> Result<ServerFrame> {
50 for capability in &self.capabilities {
51 if frame
52 .envelope
53 .required_capabilities
54 .iter()
55 .any(|required| required == capability)
56 {
57 cx.require(capability)?;
58 }
59 }
60 if frame.kind != FrameKind::Request {
61 return Err(Error::Eval(format!(
62 "agent {} only answers request frames",
63 self.name
64 )));
65 }
66 let consistency = frame.envelope.consistency;
67 notify_recorders(cx, &self.manifest.recorders, &frame)?;
68 let mut expr = eval_request_from_frame(cx, &frame)?.expr;
69 if let Some(router) = &self.manifest.router {
70 let _ = evaluate_component_expr(cx, router, expr.clone())?;
71 }
72 for retriever in &self.manifest.retrievers {
73 expr = evaluate_component_expr(cx, retriever, expr)?;
74 }
75 if let Some(planner) = &self.manifest.planner {
76 expr = evaluate_component_expr(cx, planner, expr)?;
77 }
78 if let Some(sandbox) = &self.manifest.sandbox {
79 expr = evaluate_component_expr(cx, sandbox, expr)?;
80 }
81 if is_model_request(&expr) && !self.manifest.runners.is_empty() {
82 expr = inject_manifest_tools(cx, expr, &self.manifest.tools)?;
83 expr = evaluate_component_expr(cx, &self.manifest.runners[0], expr)?;
84 } else {
85 if !self.manifest.tools.is_empty() {
86 let mut outputs = Vec::new();
87 for tool in &self.manifest.tools {
88 outputs.push(evaluate_component_expr(cx, tool, expr.clone())?);
89 }
90 expr = if outputs.len() == 1 {
91 outputs.remove(0)
92 } else {
93 Expr::List(outputs)
94 };
95 }
96 if let Some(judge) = &self.manifest.judge {
97 expr = evaluate_component_expr(cx, judge, expr)?;
98 }
99 if let Some(persona) = &self.manifest.persona {
100 expr = evaluate_component_expr(cx, persona, expr)?;
101 }
102 if let Some(voice) = &self.manifest.voice {
103 expr = evaluate_component_expr(cx, voice, expr)?;
104 }
105 }
106 let reply_value = expr_to_value(cx, &expr)?;
107 let diagnostics = cx.take_diagnostics();
108 let reply = server_frame_from_reply(
109 cx,
110 &frame.codec,
111 EvalReply {
112 value: reply_value,
113 diagnostics,
114 trace: None,
115 },
116 consistency,
117 )?;
118 notify_recorders(cx, &self.manifest.recorders, &reply)?;
119 Ok(reply)
120 }
121
122 fn as_any(&self) -> &dyn Any {
123 self
124 }
125}
126
127impl EvalSite for AgentDispatchSite {
128 fn site_kind(&self) -> &'static str {
129 "agent"
130 }
131
132 fn address(&self) -> &ServerAddress {
133 &self.address
134 }
135
136 fn codecs(&self) -> &[Symbol] {
137 &self.codecs
138 }
139
140 fn answer(&self, cx: &mut Cx, frame: ServerFrame) -> Result<ServerFrame> {
141 let runtime_site = self
142 .state
143 .lock()
144 .map_err(|_| Error::PoisonedLock("agent state"))?
145 .runtime_site
146 .clone();
147 runtime_site.answer(cx, frame)
148 }
149
150 fn as_any(&self) -> &dyn Any {
151 self
152 }
153}
154
155impl Object for Agent {
156 fn display(&self, _cx: &mut Cx) -> Result<String> {
157 Ok(format!("#<agent {}>", self.name))
158 }
159
160 fn as_any(&self) -> &dyn Any {
161 self
162 }
163}
164
165impl sim_kernel::ObjectCompat for Agent {
166 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
167 cx.factory()
168 .class_stub(sim_kernel::ClassId(0), Symbol::qualified("agent", "Agent"))
169 }
170 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
171 self.as_table(cx)?.object().as_expr(cx)
172 }
173 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
174 let state = self
175 .state
176 .lock()
177 .map_err(|_| Error::PoisonedLock("agent state"))?;
178 let manifest = self.manifest_clone()?;
179 let address_value = match &state.address {
180 Some(address) => address.as_value(cx)?,
181 None => cx.factory().symbol(Symbol::new("local"))?,
182 };
183 let components = cx.factory().list(collect_agent_components(&manifest))?;
184 cx.factory().table(vec![
185 (
186 Symbol::new("kind"),
187 cx.factory().symbol(Symbol::new("agent"))?,
188 ),
189 (Symbol::new("id"), cx.factory().string(self.id.to_string())?),
190 (Symbol::new("name"), cx.factory().symbol(self.name.clone())?),
191 (Symbol::new("components"), components),
192 (Symbol::new("address"), address_value),
193 ])
194 }
195}
196
197impl Object for AgentFabric {
198 fn display(&self, _cx: &mut Cx) -> Result<String> {
199 Ok(format!("#<swarm {}>", self.name))
200 }
201
202 fn as_any(&self) -> &dyn Any {
203 self
204 }
205}
206
207impl sim_kernel::ObjectCompat for AgentFabric {
208 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
209 cx.factory().class_stub(
210 sim_kernel::ClassId(0),
211 Symbol::qualified("swarm", "AgentFabric"),
212 )
213 }
214 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
215 self.as_table(cx)?.object().as_expr(cx)
216 }
217 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
218 let status = swarm_status_value_for_table(cx, self)?;
219 cx.factory().table(vec![
220 (
221 Symbol::new("kind"),
222 cx.factory().symbol(Symbol::new("swarm"))?,
223 ),
224 (Symbol::new("id"), cx.factory().string(self.id.to_string())?),
225 (Symbol::new("name"), cx.factory().symbol(self.name.clone())?),
226 (
227 Symbol::new("members"),
228 cx.factory().list(self.members.clone())?,
229 ),
230 (Symbol::new("status"), status),
231 ])
232 }
233 fn as_eval_fabric(&self) -> Option<&dyn sim_kernel::EvalFabric> {
234 Some(self)
235 }
236}
237
238impl Object for LoopbackStream {
239 fn display(&self, _cx: &mut Cx) -> Result<String> {
240 Ok("#<agent-stream>".to_owned())
241 }
242
243 fn as_any(&self) -> &dyn Any {
244 self
245 }
246}
247
248impl sim_kernel::ObjectCompat for LoopbackStream {
249 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
250 cx.factory()
251 .class_stub(sim_kernel::ClassId(0), Symbol::qualified("agent", "Stream"))
252 }
253 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
254 let mut chunks = Vec::new();
255 for value in self.handle.buffered_values()? {
256 chunks.push(value.object().as_expr(cx)?);
257 }
258 Ok(Expr::List(chunks))
259 }
260 fn as_stream(&self) -> Option<&dyn sim_kernel::Stream> {
261 Some(self.handle.as_ref())
262 }
263}
264
265impl EvalSite for RuntimeValueSite {
266 fn site_kind(&self) -> &'static str {
267 self.kind
268 }
269
270 fn address(&self) -> &ServerAddress {
271 &self.address
272 }
273
274 fn codecs(&self) -> &[Symbol] {
275 &self.codecs
276 }
277
278 fn answer(&self, cx: &mut Cx, frame: ServerFrame) -> Result<ServerFrame> {
279 if let Some(agent) = self.value.object().downcast_ref::<Agent>() {
280 return agent.site()?.answer(cx, frame);
281 }
282 if let Some(fabric) = self.value.object().downcast_ref::<AgentFabric>() {
283 return fabric.answer(cx, frame);
284 }
285 Err(Error::TypeMismatch {
286 expected: "agent or swarm value",
287 found: "non-routable value",
288 })
289 }
290
291 fn as_any(&self) -> &dyn Any {
292 self
293 }
294}
295
296fn notify_recorders(cx: &mut Cx, recorders: &[Value], frame: &ServerFrame) -> Result<()> {
297 for recorder in recorders {
298 let site = site_from_value(recorder)?;
299 let mut notify = frame.clone();
300 notify.kind = FrameKind::Notify;
301 let _ = site.answer(cx, notify)?;
302 }
303 Ok(())
304}
305
306pub(crate) fn evaluate_component_expr(cx: &mut Cx, value: &Value, expr: Expr) -> Result<Expr> {
307 if let Some(connection) = connection_from_value(value) {
308 return connection
309 .request(cx, expr, None, Vec::new())?
310 .object()
311 .as_expr(cx);
312 }
313 let site = site_from_value(value)?;
314 let request = ServerFrame::from_expr(
315 cx,
316 first_codec(site.codecs()),
317 FrameKind::Request,
318 &expr,
319 sim_kernel::Consistency::LocalFirst,
320 Vec::new(),
321 false,
322 )?;
323 let reply = site.answer(cx, request)?;
324 eval_reply_from_frame(cx, &reply)?
325 .value
326 .object()
327 .as_expr(cx)
328}
329
330#[allow(dead_code)]
331fn _assert_types(
332 _: &Tool,
333 _: &AgentComponent,
334 _: &WorkingMemory,
335 _: &FileMemory,
336 _: &VectorMemory,
337 _: &BlackboardMemory,
338 _: &PersonaMemory,
339) {
340}