atman-runtime 1.9.1

atman flow execution runtime: evaluator, tool dispatch, provider dispatch, executor, memory stores
Documentation
use std::sync::Arc;

use atman_dsl::parse::parse_file;
use atman_runtime::memory::goal::GoalStore;
use atman_runtime::message::{Message, MessageOrigin, MessageRole};
use atman_runtime::{Executor, Session, Value};

#[tokio::test]
async fn recent_turns_returns_empty_before_any_message() {
    let tmp = tempfile::tempdir().unwrap();
    let session = std::sync::Arc::new(Session::open(tmp.path()).unwrap());
    let ex = Executor::new();
    atman_runtime::tools::register_tier_zero(&ex.tools);
    let todo = Arc::new(atman_runtime::memory::TodoStore::at(session.dir()));
    let conf = Arc::new(atman_runtime::memory::ConfessionStore::at(session.dir()));
    let goal = Arc::new(GoalStore::at(session.dir()));
    let plan = Arc::new(atman_runtime::memory::PlanStore::at(session.dir()));
    atman_runtime::tools::register_memory(&ex.tools, todo, conf, goal, plan);

    let src = r#"flow t() -> int {
    result = memory.recent_turns(n: 5)
    return len(result.items)
}
"#;
    let file = parse_file(src).unwrap();
    let user_msg = Message::user_text(atman_runtime::event::TurnId::now(), "run");
    session.begin_turn(user_msg);
    let out = ex
        .run_in_turn(&file, "t", vec![], None, Some(session.clone()))
        .await
        .unwrap();
    session.end_turn();

    match out {
        Value::Int(n) => assert!(n <= 1, "want zero or the just-emitted user msg, got {n}"),
        other => panic!("want int, got {other:?}"),
    }
}

#[tokio::test]
async fn recent_turns_picks_up_appended_messages() {
    let tmp = tempfile::tempdir().unwrap();
    let session = std::sync::Arc::new(Session::open(tmp.path()).unwrap());

    for role in ["hi", "world"] {
        let m = Message::user_text(atman_runtime::event::TurnId::now(), role);
        session.append_message(m, None);
    }

    let ex = Executor::new();
    atman_runtime::tools::register_tier_zero(&ex.tools);
    let todo = Arc::new(atman_runtime::memory::TodoStore::at(session.dir()));
    let conf = Arc::new(atman_runtime::memory::ConfessionStore::at(session.dir()));
    let goal = Arc::new(GoalStore::at(session.dir()));
    let plan = Arc::new(atman_runtime::memory::PlanStore::at(session.dir()));
    atman_runtime::tools::register_memory(&ex.tools, todo, conf, goal, plan);

    let src = r#"flow t() -> int {
    result = memory.recent_turns(n: 5)
    return len(result.items)
}
"#;
    let file = parse_file(src).unwrap();
    tokio::time::sleep(std::time::Duration::from_millis(80)).await;
    let user_msg = Message::user_text(atman_runtime::event::TurnId::now(), "run");
    session.begin_turn(user_msg);
    let out = ex
        .run_in_turn(&file, "t", vec![], None, Some(session.clone()))
        .await
        .unwrap();
    session.end_turn();

    match out {
        Value::Int(n) => assert!(n >= 2, "want at least the 2 appended msgs, got {n}"),
        other => panic!("want int, got {other:?}"),
    }
}

#[tokio::test]
async fn recent_turns_caps_output_at_n() {
    let tmp = tempfile::tempdir().unwrap();
    let session = std::sync::Arc::new(Session::open(tmp.path()).unwrap());

    for i in 0..8 {
        let m = Message {
            role: MessageRole::User,
            parts: vec![atman_runtime::message::MessagePart::Text {
                text: format!("msg{i}"),
            }],
            turn_id: atman_runtime::event::TurnId::now(),
            origin: MessageOrigin::User,
        };
        session.append_message(m, None);
    }

    let ex = Executor::new();
    atman_runtime::tools::register_tier_zero(&ex.tools);
    let todo = Arc::new(atman_runtime::memory::TodoStore::at(session.dir()));
    let conf = Arc::new(atman_runtime::memory::ConfessionStore::at(session.dir()));
    let goal = Arc::new(GoalStore::at(session.dir()));
    let plan = Arc::new(atman_runtime::memory::PlanStore::at(session.dir()));
    atman_runtime::tools::register_memory(&ex.tools, todo, conf, goal, plan);

    let src = r#"flow t() -> int {
    result = memory.recent_turns(n: 3)
    return len(result.items)
}
"#;
    let file = parse_file(src).unwrap();
    tokio::time::sleep(std::time::Duration::from_millis(80)).await;
    let user_msg = Message::user_text(atman_runtime::event::TurnId::now(), "run");
    session.begin_turn(user_msg);
    let out = ex
        .run_in_turn(&file, "t", vec![], None, Some(session.clone()))
        .await
        .unwrap();
    session.end_turn();

    match out {
        Value::Int(n) => assert!(
            (2..=3).contains(&n),
            "want cap of 3 (or 2 if the just-begun turn isn't flushed yet), got {n}"
        ),
        other => panic!("want int, got {other:?}"),
    }
}