Skip to main content

sim_lib_agent/agents/model/
runtime.rs

1use 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}