sim-lib-agent 0.1.1

Agent runtime surfaces for SIM.
Documentation
use super::agent_r18_support::{
    fixed_reply_connection, map_expr_field, register_bid_worker, register_connection,
    star_hub_connection, tagged_append_connection, verifier_connection,
};
use super::support::{eval_cx, flatten_text, install_agent_lib, install_roundtrip_codecs};
use sim_kernel::{Args, Expr, Symbol};
use sim_lib_server::Connection;

#[test]
fn r18_ring_cycles_roles_and_visits_agents_in_order() {
    let mut cx = eval_cx();
    install_roundtrip_codecs(&mut cx);
    install_agent_lib(&mut cx).unwrap();
    register_connection(
        &mut cx,
        Symbol::qualified("test", "ring-a"),
        tagged_append_connection("a"),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "ring-b"),
        tagged_append_connection("b"),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "ring-c"),
        tagged_append_connection("c"),
    );

    let ring = cx
        .call_function(
            &Symbol::qualified("topology", "ring"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":agents")).unwrap(),
                cx.factory()
                    .expr(Expr::List(vec![
                        Expr::Symbol(Symbol::qualified("test", "ring-a")),
                        Expr::Symbol(Symbol::qualified("test", "ring-b")),
                        Expr::Symbol(Symbol::qualified("test", "ring-c")),
                    ]))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":role-cycle")).unwrap(),
                cx.factory()
                    .expr(Expr::List(vec![
                        Expr::Symbol(Symbol::new("planner")),
                        Expr::Symbol(Symbol::new("worker")),
                        Expr::Symbol(Symbol::new("critic")),
                    ]))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":max-turns")).unwrap(),
                cx.factory()
                    .number_literal(Symbol::qualified("numbers", "f64"), "3".to_owned())
                    .unwrap(),
            ]),
        )
        .unwrap();

    let reply = ring
        .object()
        .downcast_ref::<Connection>()
        .unwrap()
        .request(&mut cx, Expr::List(Vec::new()), None, Vec::new())
        .unwrap();
    let expr = reply.object().as_expr(&mut cx).unwrap();
    let text = flatten_text(&expr);
    assert!(text.contains("a:planner"));
    assert!(text.contains("b:worker"));
    assert!(text.contains("c:critic"));
}

#[test]
fn r18_star_fans_out_and_hub_sees_all_spoke_replies() {
    let mut cx = eval_cx();
    install_roundtrip_codecs(&mut cx);
    install_agent_lib(&mut cx).unwrap();
    register_connection(
        &mut cx,
        Symbol::qualified("test", "hub"),
        star_hub_connection(),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "spoke-b"),
        fixed_reply_connection("spoke-b"),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "spoke-c"),
        fixed_reply_connection("spoke-c"),
    );

    let topology = cx
        .call_function(
            &Symbol::qualified("topology", "star"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":hub")).unwrap(),
                cx.resolve_value(&Symbol::qualified("test", "hub")).unwrap(),
                cx.factory().symbol(Symbol::new(":spokes")).unwrap(),
                cx.factory()
                    .expr(Expr::List(vec![
                        Expr::Symbol(Symbol::qualified("test", "spoke-b")),
                        Expr::Symbol(Symbol::qualified("test", "spoke-c")),
                    ]))
                    .unwrap(),
            ]),
        )
        .unwrap();
    let expr = topology
        .object()
        .downcast_ref::<Connection>()
        .unwrap()
        .request(&mut cx, Expr::String("task".to_owned()), None, Vec::new())
        .unwrap()
        .object()
        .as_expr(&mut cx)
        .unwrap();
    let text = flatten_text(&expr);
    assert!(text.contains("spoke-b"));
    assert!(text.contains("spoke-c"));
    assert!(text.contains("merge-count"));
}

#[test]
fn r18_mesh_uses_real_judge_to_pick_best_answer() {
    let mut cx = eval_cx();
    install_roundtrip_codecs(&mut cx);
    install_agent_lib(&mut cx).unwrap();
    register_connection(
        &mut cx,
        Symbol::qualified("test", "mesh-a"),
        fixed_reply_connection("perfect target"),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "mesh-b"),
        fixed_reply_connection("target maybe"),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "mesh-c"),
        fixed_reply_connection("noise"),
    );

    let judge = cx
        .call_function(
            &Symbol::qualified("judge", "rubric"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":reference")).unwrap(),
                cx.factory().string("perfect target".to_owned()).unwrap(),
            ]),
        )
        .unwrap();
    let topology = cx
        .call_function(
            &Symbol::qualified("topology", "mesh"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":agents")).unwrap(),
                cx.factory()
                    .expr(Expr::List(vec![
                        Expr::Symbol(Symbol::qualified("test", "mesh-a")),
                        Expr::Symbol(Symbol::qualified("test", "mesh-b")),
                        Expr::Symbol(Symbol::qualified("test", "mesh-c")),
                    ]))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":judge")).unwrap(),
                judge,
                cx.factory().symbol(Symbol::new(":max-rounds")).unwrap(),
                cx.factory()
                    .number_literal(Symbol::qualified("numbers", "f64"), "2".to_owned())
                    .unwrap(),
            ]),
        )
        .unwrap();
    let expr = topology
        .object()
        .downcast_ref::<Connection>()
        .unwrap()
        .request(&mut cx, Expr::String("seed".to_owned()), None, Vec::new())
        .unwrap()
        .object()
        .as_expr(&mut cx)
        .unwrap();
    assert_eq!(
        map_expr_field(&expr, "candidate"),
        Expr::String("perfect target".to_owned())
    );
}

#[test]
fn r18_market_routes_to_lowest_bidder() {
    let mut cx = eval_cx();
    install_roundtrip_codecs(&mut cx);
    install_agent_lib(&mut cx).unwrap();

    let _cheap = register_bid_worker(
        &mut cx,
        Symbol::qualified("test", "cheap"),
        1.0,
        "cheap won",
    );
    let _expensive = register_bid_worker(
        &mut cx,
        Symbol::qualified("test", "expensive"),
        9.0,
        "expensive won",
    );
    let router = cx
        .call_function(
            &Symbol::qualified("router", "bid"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":targets")).unwrap(),
                cx.factory()
                    .expr(Expr::List(vec![
                        Expr::Symbol(Symbol::qualified("test", "expensive")),
                        Expr::Symbol(Symbol::qualified("test", "cheap")),
                    ]))
                    .unwrap(),
            ]),
        )
        .unwrap();
    let topology = cx
        .call_function(
            &Symbol::qualified("topology", "market"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":workers")).unwrap(),
                cx.factory()
                    .expr(Expr::List(vec![
                        Expr::Symbol(Symbol::qualified("test", "expensive")),
                        Expr::Symbol(Symbol::qualified("test", "cheap")),
                    ]))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":router")).unwrap(),
                router,
            ]),
        )
        .unwrap();
    let expr = topology
        .object()
        .downcast_ref::<Connection>()
        .unwrap()
        .request(&mut cx, Expr::String("route".to_owned()), None, Vec::new())
        .unwrap()
        .object()
        .as_expr(&mut cx)
        .unwrap();
    assert!(flatten_text(&expr).contains("cheap"));
    assert!(flatten_text(&expr).contains("cheap won"));
}

#[test]
fn r18_debate_returns_verdict_winner_and_transcript() {
    let mut cx = eval_cx();
    install_roundtrip_codecs(&mut cx);
    install_agent_lib(&mut cx).unwrap();

    let judge = cx
        .call_function(
            &Symbol::qualified("judge", "rubric"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":reference")).unwrap(),
                cx.factory().string("pro wins evidence".to_owned()).unwrap(),
            ]),
        )
        .unwrap();
    register_connection(
        &mut cx,
        Symbol::qualified("test", "debate-pro"),
        fixed_reply_connection("pro wins evidence"),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "debate-con"),
        fixed_reply_connection("con loses"),
    );
    let topology = cx
        .call_function(
            &Symbol::qualified("topology", "debate"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":pro")).unwrap(),
                cx.resolve_value(&Symbol::qualified("test", "debate-pro"))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":con")).unwrap(),
                cx.resolve_value(&Symbol::qualified("test", "debate-con"))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":judge")).unwrap(),
                judge,
                cx.factory().symbol(Symbol::new(":rounds")).unwrap(),
                cx.factory()
                    .number_literal(Symbol::qualified("numbers", "f64"), "1".to_owned())
                    .unwrap(),
            ]),
        )
        .unwrap();
    let expr = topology
        .object()
        .downcast_ref::<Connection>()
        .unwrap()
        .request(&mut cx, Expr::String("topic".to_owned()), None, Vec::new())
        .unwrap()
        .object()
        .as_expr(&mut cx)
        .unwrap();
    assert_eq!(
        map_expr_field(&expr, "winner"),
        Expr::String("pro".to_owned())
    );
    let text = flatten_text(&expr);
    assert!(text.contains("pro wins evidence"));
    assert!(text.contains("con loses"));
}

#[test]
fn r18_speculate_verify_uses_fast_path_and_handles_mismatch() {
    let mut cx = eval_cx();
    install_roundtrip_codecs(&mut cx);
    install_agent_lib(&mut cx).unwrap();
    register_connection(
        &mut cx,
        Symbol::qualified("test", "spec-fast"),
        fixed_reply_connection("fast"),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "verify-fast"),
        verifier_connection("fast", "slow"),
    );
    register_connection(
        &mut cx,
        Symbol::qualified("test", "verify-different"),
        verifier_connection("different", "slow"),
    );

    let agree = cx
        .call_function(
            &Symbol::qualified("topology", "speculate-verify"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":speculator")).unwrap(),
                cx.resolve_value(&Symbol::qualified("test", "spec-fast"))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":verifier")).unwrap(),
                cx.resolve_value(&Symbol::qualified("test", "verify-fast"))
                    .unwrap(),
            ]),
        )
        .unwrap();
    let agree_expr = agree
        .object()
        .downcast_ref::<Connection>()
        .unwrap()
        .request(&mut cx, Expr::String("task".to_owned()), None, Vec::new())
        .unwrap()
        .object()
        .as_expr(&mut cx)
        .unwrap();
    assert_eq!(
        map_expr_field(&agree_expr, "result"),
        Expr::String("fast".to_owned())
    );
    assert_eq!(map_expr_field(&agree_expr, "agreed"), Expr::Bool(true));

    let mismatch = cx
        .call_function(
            &Symbol::qualified("topology", "speculate-verify"),
            Args::new(vec![
                cx.factory().symbol(Symbol::new(":speculator")).unwrap(),
                cx.resolve_value(&Symbol::qualified("test", "spec-fast"))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":verifier")).unwrap(),
                cx.resolve_value(&Symbol::qualified("test", "verify-different"))
                    .unwrap(),
                cx.factory().symbol(Symbol::new(":on-mismatch")).unwrap(),
                cx.factory().symbol(Symbol::new("retry")).unwrap(),
            ]),
        )
        .unwrap();
    let mismatch_expr = mismatch
        .object()
        .downcast_ref::<Connection>()
        .unwrap()
        .request(&mut cx, Expr::String("task".to_owned()), None, Vec::new())
        .unwrap()
        .object()
        .as_expr(&mut cx)
        .unwrap();
    assert_eq!(map_expr_field(&mismatch_expr, "agreed"), Expr::Bool(false));
    assert_eq!(
        map_expr_field(&mismatch_expr, "result"),
        Expr::String("slow".to_owned())
    );
}