sim-lib-agent 0.1.1

Agent runtime surfaces for SIM.
Documentation
use super::topology_runtime::{
    DebateSession, MeshSession, RingSession, evaluate_connection, map_field, number_expr,
    number_field, reply_expr, text_label,
};
use crate::{expr_to_value, memory::flatten_expr_text};
use sim_kernel::{Cx, Error, EvalReply, Expr, Result, Symbol, Value};
use sim_lib_server::{FrameEnvelope, ServerAddress, ServerFrame, server_frame_from_reply};
use std::sync::{Arc, Mutex, OnceLock};

pub(super) fn reply_state(cx: &mut Cx, frame: &ServerFrame, state: Expr) -> Result<ServerFrame> {
    reply_expr_value(cx, frame, state)
}

pub(super) fn reply_expr_value(
    cx: &mut Cx,
    frame: &ServerFrame,
    expr: Expr,
) -> Result<ServerFrame> {
    let value = expr_to_value(cx, &expr)?;
    let diagnostics = cx.take_diagnostics();
    server_frame_from_reply(
        cx,
        &frame.codec,
        EvalReply {
            value,
            diagnostics,
            trace: None,
        },
        frame.envelope.consistency,
    )
}

pub(super) fn ring_state(session: &Arc<Mutex<RingSession>>) -> Result<Expr> {
    let session = session
        .lock()
        .map_err(|_| Error::PoisonedLock("ring session"))?;
    Ok(Expr::Map(vec![
        (Expr::Symbol(Symbol::new("done")), Expr::Bool(session.done)),
        (Expr::Symbol(Symbol::new("result")), session.current.clone()),
        (
            Expr::Symbol(Symbol::new("transcript")),
            Expr::List(session.transcript.clone()),
        ),
        (
            Expr::Symbol(Symbol::new("turns-used")),
            number_expr(session.turns_used),
        ),
    ]))
}

pub(super) fn mesh_state(session: &Arc<Mutex<MeshSession>>) -> Result<Expr> {
    let session = session
        .lock()
        .map_err(|_| Error::PoisonedLock("mesh session"))?;
    Ok(Expr::Map(vec![
        (Expr::Symbol(Symbol::new("done")), Expr::Bool(session.done)),
        (
            Expr::Symbol(Symbol::new("candidate")),
            session.candidate.clone(),
        ),
        (
            Expr::Symbol(Symbol::new("score")),
            number_expr(session.best_score.unwrap_or(0.0)),
        ),
        (
            Expr::Symbol(Symbol::new("transcript")),
            Expr::List(session.transcript.clone()),
        ),
        (
            Expr::Symbol(Symbol::new("rounds-used")),
            number_expr(session.rounds_used),
        ),
    ]))
}

pub(super) fn debate_state(session: &Arc<Mutex<DebateSession>>) -> Result<Expr> {
    let session = session
        .lock()
        .map_err(|_| Error::PoisonedLock("debate session"))?;
    Ok(Expr::Map(vec![
        (Expr::Symbol(Symbol::new("done")), Expr::Bool(session.done)),
        (Expr::Symbol(Symbol::new("task")), session.task.clone()),
        (
            Expr::Symbol(Symbol::new("transcript")),
            Expr::List(session.transcript.clone()),
        ),
        (
            Expr::Symbol(Symbol::new("turns-used")),
            number_expr(session.turns_used),
        ),
    ]))
}

pub(super) fn judge_expr(
    cx: &mut Cx,
    judge: &Value,
    expr: Expr,
    parent: &FrameEnvelope,
) -> Result<Expr> {
    let connection = super::shared::agent_connection_for_value(judge.clone())?;
    let reply = evaluate_connection(cx, &connection, expr, Some(Symbol::new("judge")), parent)?;
    reply_expr(cx, &reply)
}

pub(super) fn judge_score(
    cx: &mut Cx,
    judge: &Value,
    candidate: Expr,
    parent: &FrameEnvelope,
) -> Result<f64> {
    let verdict = judge_expr(cx, judge, candidate, parent)?;
    number_field(&verdict, "score")
}

pub(super) fn side_case(transcript: &[Expr], side: &str) -> Expr {
    Expr::String(
        transcript
            .iter()
            .filter_map(|entry| {
                let matches_side = matches!(
                    map_field(entry, "side"),
                    Some(Expr::String(actual)) if actual == side
                );
                if !matches_side {
                    return None;
                }
                map_field(entry, "value").map(flatten_expr_text)
            })
            .collect::<Vec<_>>()
            .join(" "),
    )
}

pub(super) fn select_market_winner(
    cx: &mut Cx,
    workers: &[Value],
    router: &Value,
    task: &Expr,
    parent: &FrameEnvelope,
) -> Result<(Value, f64)> {
    let router_conn = super::shared::agent_connection_for_value(router.clone())?;
    let routed = evaluate_connection(
        cx,
        &router_conn,
        task.clone(),
        Some(Symbol::new("router")),
        parent,
    )?;
    let decision = reply_expr(cx, &routed)?;
    if let Some(Expr::String(label)) = map_field(&decision, "target")
        && let Some(worker) = workers.iter().find(|worker| text_label(worker) == *label)
    {
        return Ok((
            worker.clone(),
            number_field(&decision, "bid").unwrap_or(0.0),
        ));
    }
    if let Some(Expr::Symbol(label)) = map_field(&decision, "target")
        && let Some(worker) = workers
            .iter()
            .find(|worker| text_label(worker) == label.to_string())
    {
        return Ok((
            worker.clone(),
            number_field(&decision, "bid").unwrap_or(0.0),
        ));
    }
    let mut best = None::<(Value, f64)>;
    for worker in workers {
        let connection = super::shared::agent_connection_for_value(worker.clone())?;
        let reply = evaluate_connection(
            cx,
            &connection,
            Expr::List(vec![Expr::Symbol(Symbol::new("bid")), task.clone()]),
            Some(Symbol::new("worker")),
            parent,
        )?;
        let bid_expr = reply_expr(cx, &reply)?;
        let bid = match bid_expr {
            Expr::Number(number) => number.canonical.parse::<f64>().unwrap_or(f64::INFINITY),
            Expr::Map(_) => number_field(&bid_expr, "bid").unwrap_or(f64::INFINITY),
            _ => f64::INFINITY,
        };
        if best.as_ref().is_none_or(|(_, current)| bid < *current) {
            best = Some((worker.clone(), bid));
        }
    }
    best.ok_or_else(|| Error::Eval("market auction found no bidders".to_owned()))
}

pub(super) fn local_address() -> &'static ServerAddress {
    static LOCAL: OnceLock<ServerAddress> = OnceLock::new();
    LOCAL.get_or_init(|| ServerAddress::Local)
}