use std::{
collections::VecDeque,
path::Path,
sync::{Arc, Mutex},
time::Duration,
};
use async_trait::async_trait;
use basis::{
AllowAll, ApprovalAnswer, ApprovalDecision, ApprovalRequest, Approver, Bound, CollectingSink,
Event, RunConfig, RunUsage, SpawnTool, TurnOptions, approval::ApprovalGate,
run::prepare_with_session, tools::SPAWN,
};
use mentra::{
BuiltinProvider, ContentBlock, ModelInfo, Role, Runtime, RuntimePolicy, Session, TokenUsage,
agent::{AgentConfig, ToolProfile, WorkspaceConfig},
provider::{
Provider, ProviderDescriptor, ProviderError, ProviderEventStream, Request, Response,
provider_event_stream_from_response,
},
runtime::VolatileRuntimeStore,
session::{PermissionRuleScope, RememberedRule, RuleKey},
};
use serde_json::{Value, json};
const NOT_STUCK: Duration = Duration::from_secs(20);
const RAN: &str = "the-command-ran";
#[derive(Debug, Clone)]
struct Turn {
content: Vec<ContentBlock>,
tokens: u64,
}
impl Turn {
fn calling(id: &str, input: &str) -> Self {
Self {
content: vec![ContentBlock::ToolUse {
id: id.to_string(),
name: SPAWN.to_string(),
input: json!({ "input": input }),
}],
tokens: 0,
}
}
fn saying(text: &str) -> Self {
Self {
content: vec![ContentBlock::text(text)],
tokens: 0,
}
}
fn costing(self, tokens: u64) -> Self {
Self { tokens, ..self }
}
fn usage(&self) -> Option<TokenUsage> {
(self.tokens > 0).then(|| TokenUsage {
input_tokens: Some(self.tokens),
output_tokens: Some(0),
total_tokens: Some(self.tokens),
..TokenUsage::default()
})
}
}
#[derive(Debug, Clone)]
struct Asked {
tools: Vec<String>,
transcript: String,
}
struct ScriptedProvider {
model: ModelInfo,
turns: Mutex<VecDeque<Turn>>,
asked: Arc<Mutex<Vec<Asked>>>,
}
impl ScriptedProvider {
fn new(model: ModelInfo, turns: Vec<Turn>) -> (Self, Arc<Mutex<Vec<Asked>>>) {
let asked = Arc::new(Mutex::new(Vec::new()));
let provider = Self {
model,
turns: Mutex::new(turns.into()),
asked: Arc::clone(&asked),
};
(provider, asked)
}
}
#[async_trait]
impl Provider for ScriptedProvider {
fn descriptor(&self) -> ProviderDescriptor {
ProviderDescriptor::new(self.model.provider.clone())
}
async fn list_models(&self) -> Result<Vec<ModelInfo>, ProviderError> {
Ok(vec![self.model.clone()])
}
async fn stream(&self, request: Request<'_>) -> Result<ProviderEventStream, ProviderError> {
self.asked.lock().expect("not poisoned").push(Asked {
tools: request.tools.iter().map(|tool| tool.name.clone()).collect(),
transcript: format!("{:?}", request.messages),
});
let turn = self
.turns
.lock()
.expect("not poisoned")
.pop_front()
.unwrap_or_else(|| Turn::saying("done"));
Ok(provider_event_stream_from_response(Response {
id: "scripted".to_string(),
model: self.model.id.clone(),
role: Role::Assistant,
usage: turn.usage(),
content: turn.content,
stop_reason: None,
}))
}
}
struct Script {
turns: Vec<Turn>,
commands: bool,
rules: Vec<RememberedRule>,
options: TurnOptions,
}
impl Script {
fn new(turns: Vec<Turn>) -> Self {
Self {
turns,
commands: true,
rules: Vec::new(),
options: TurnOptions::default(),
}
}
fn without_commands(self) -> Self {
Self {
commands: false,
..self
}
}
fn remembering(self, rule: RememberedRule) -> Self {
let rules = self.rules.into_iter().chain([rule]).collect();
Self { rules, ..self }
}
fn with_token_budget(self, budget: u64) -> Self {
Self {
options: self.options.with_token_budget(budget),
..self
}
}
}
fn runtime(workspace: &Path, turns: Vec<Turn>, commands: bool) -> (Runtime, ModelInfo, Requests) {
let model = ModelInfo::new("scripted-model", BuiltinProvider::OpenAI);
let (provider, asked) = ScriptedProvider::new(model.clone(), turns);
let runtime = Runtime::builder()
.with_provider_instance(provider)
.with_store(VolatileRuntimeStore::new())
.with_policy(
RuntimePolicy::workspace_bounded(workspace)
.allow_shell_commands(commands)
.allow_background_commands(commands),
)
.with_tool_authorizer(ApprovalGate::new())
.with_tool(SpawnTool::new())
.build()
.expect("runtime builds");
(runtime, model, Requests(asked))
}
fn agent(workspace: &Path) -> AgentConfig {
AgentConfig {
tool_profile: ToolProfile::hide(["shell", "background_run", "task"]),
workspace: WorkspaceConfig {
base_dir: workspace.to_path_buf(),
..Default::default()
},
..Default::default()
}
}
fn session(runtime: &Runtime, workspace: &Path, model: ModelInfo) -> Session {
runtime
.create_session_with_config("test", model, agent(workspace))
.expect("session")
}
fn config(workspace: &Path) -> RunConfig {
RunConfig::new(workspace, "do the thing").with_context(basis::ContextConfig {
file_name: "AGENTS.md".to_string(),
global_dir: None,
walk_parents: false,
})
}
struct Requests(Arc<Mutex<Vec<Asked>>>);
impl Requests {
fn all(&self) -> Vec<Asked> {
self.0.lock().expect("not poisoned").clone()
}
fn roster(&self, index: usize) -> Vec<String> {
self.all()
.get(index)
.map(|asked| asked.tools.clone())
.unwrap_or_default()
}
fn any_transcript_contains(&self, needle: &str) -> bool {
self.all()
.iter()
.any(|asked| asked.transcript.contains(needle))
}
}
struct Recording<A> {
inner: A,
seen: Arc<Mutex<Vec<ApprovalRequest>>>,
}
#[async_trait]
impl<A: Approver> Approver for Recording<A> {
async fn approve(&mut self, request: &ApprovalRequest) -> ApprovalAnswer {
self.seen
.lock()
.expect("not poisoned")
.push(request.clone());
self.inner.approve(request).await
}
}
struct RefusesForGood;
const REFUSAL: &str = "this run does not run commands";
#[async_trait]
impl Approver for RefusesForGood {
async fn approve(&mut self, _request: &ApprovalRequest) -> ApprovalAnswer {
ApprovalAnswer::new(ApprovalDecision::DenyForSession).because(REFUSAL)
}
}
struct Run {
events: Vec<Event>,
asked: Vec<ApprovalRequest>,
requests: Requests,
stopped_by: Option<Bound>,
usage: RunUsage,
}
impl Run {
fn results(&self) -> Vec<(bool, String)> {
self.events
.iter()
.filter_map(|event| match event {
Event::ToolCompleted {
tool_name,
is_error,
summary,
..
} if tool_name == SPAWN => Some((*is_error, summary.clone())),
_ => None,
})
.collect()
}
fn first_result(&self) -> (bool, String) {
self.results()
.into_iter()
.next()
.expect("spawn must have completed at least once")
}
}
async fn drive<A: Approver>(workspace: &Path, script: Script, approver: A) -> Run {
let (runtime, model, requests) = runtime(workspace, script.turns, script.commands);
let session = session(&runtime, workspace, model);
for rule in script.rules {
session.rule_store().add_rule(rule);
}
let seen = Arc::new(Mutex::new(Vec::new()));
let mut prepared =
prepare_with_session(session, &config(workspace), "openai", "scripted-model")
.expect("prepared");
let report = tokio::time::timeout(
NOT_STUCK,
prepared.execute_with_approver_and_options(
CollectingSink::new(),
Recording {
inner: approver,
seen: Arc::clone(&seen),
},
script.options,
),
)
.await
.expect("the run must not hang waiting on an unanswered approval")
.expect("the run completes");
let asked = seen.lock().expect("not poisoned").clone();
Run {
events: report.sink.into_events(),
asked,
requests,
stopped_by: report.stopped_by,
usage: report.usage,
}
}
async fn one_call<A: Approver>(workspace: &Path, input: &str, approver: A) -> Run {
drive(
workspace,
Script::new(vec![Turn::calling("call-0", input)]),
approver,
)
.await
}
#[tokio::test]
async fn the_model_is_offered_one_door() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = one_call(workspace.path(), &format!("!echo {RAN}"), AllowAll).await;
let roster = run.requests.roster(0);
assert!(
roster.contains(&SPAWN.to_string()),
"the one door has to be on the roster: {roster:?}"
);
for replaced in ["shell", "background_run", "task"] {
assert!(
!roster.contains(&replaced.to_string()),
"{replaced} is still offered alongside spawn: {roster:?}"
);
}
}
#[tokio::test]
async fn a_command_is_answered_before_it_runs_and_then_runs() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = one_call(workspace.path(), &format!("!echo {RAN}"), AllowAll).await;
assert_eq!(
run.asked.len(),
1,
"a command is never waved through: {:?}",
run.asked
);
assert_eq!(run.asked[0].tool_name, SPAWN);
let (failed, output) = run.first_result();
assert!(!failed, "an approved command runs: {output}");
assert!(
output.contains(RAN),
"and basis reads its output back: {output}"
);
}
#[tokio::test]
async fn the_approver_is_shown_the_parsed_call_and_not_the_string() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = one_call(workspace.path(), &format!("!echo {RAN}"), AllowAll).await;
let input = &run.asked[0].input;
assert_eq!(input["mode"], "command");
assert_eq!(input["body"], format!("echo {RAN}"));
assert_eq!(
input["cwd"],
Value::String(workspace.path().to_string_lossy().into_owned()),
"an approver cannot judge a command without knowing where it runs"
);
}
#[tokio::test]
async fn a_delegation_reaches_the_approver_as_a_delegation() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = one_call(workspace.path(), "summarise the README", AllowAll).await;
assert_eq!(run.asked.len(), 1, "delegation is consequential too");
assert_eq!(run.asked[0].input["mode"], "agent");
assert_eq!(run.asked[0].input["body"], "summarise the README");
}
#[tokio::test]
async fn a_refused_command_does_not_run() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = one_call(workspace.path(), &format!("!echo {RAN}"), RefusesForGood).await;
let (failed, output) = run.first_result();
assert!(failed, "a refused command fails visibly: {output}");
assert!(
!output.contains(RAN),
"and its output cannot exist, because it never ran: {output}"
);
assert!(output.contains(REFUSAL), "the model reads why: {output}");
}
#[tokio::test]
async fn a_remembered_refusal_repeats_its_reason_with_nobody_asked() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = drive(
workspace.path(),
Script::new(vec![
Turn::calling("call-0", &format!("!echo {RAN}")),
Turn::calling("call-1", &format!("!echo {RAN}")),
]),
RefusesForGood,
)
.await;
assert_eq!(
run.asked.len(),
1,
"the second call must be answered by the rule, not by the approver"
);
let results = run.results();
assert_eq!(results.len(), 2, "both calls completed");
for (failed, output) in &results {
assert!(failed, "{output}");
assert!(output.contains(REFUSAL), "{output}");
}
assert!(
results[1].1.contains("remembered"),
"the repeat says it is a repeat, or the model reads it as a fresh no: {}",
results[1].1
);
}
#[tokio::test]
async fn a_remembered_answer_on_the_name_covers_both_modes() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = drive(
workspace.path(),
Script::new(vec![
Turn::calling("call-0", &format!("!echo {RAN}")),
Turn::calling("call-1", "summarise the README"),
]),
RefusesForGood,
)
.await;
assert_eq!(run.asked.len(), 1);
let results = run.results();
assert!(
results[1].0 && results[1].1.contains(REFUSAL),
"the delegation was answered by the command's rule: {:?}",
results[1]
);
}
#[tokio::test]
async fn a_pattern_rule_is_a_command_allowlist_expressible_as_data() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = drive(
workspace.path(),
Script::new(vec![Turn::calling("call-0", &format!("!echo {RAN}"))]).remembering(
RememberedRule {
key: RuleKey {
tool_name: SPAWN.to_string(),
pattern: Some(format!("**\"body\":\"echo {RAN}\"**")),
},
allow: true,
scope: PermissionRuleScope::Session,
reason: None,
},
),
RefusesForGood,
)
.await;
assert!(
run.asked.is_empty(),
"an allowlisted command must never reach the reviewer: {:?}",
run.asked
);
let (failed, output) = run.first_result();
assert!(!failed, "{output}");
assert!(output.contains(RAN), "{output}");
}
#[tokio::test]
async fn no_shell_still_refuses_command_mode() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = drive(
workspace.path(),
Script::new(vec![Turn::calling("call-0", &format!("!echo {RAN}"))]).without_commands(),
AllowAll,
)
.await;
let (failed, output) = run.first_result();
assert!(failed, "a command must not succeed with commands off");
assert!(!output.contains(RAN), "and nothing may have run: {output}");
assert!(
output.contains("Shell command execution is disabled"),
"the refusal has to say what refused it: {output}"
);
}
#[tokio::test]
async fn delegation_hands_work_over_and_reads_the_answer_back() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = drive(
workspace.path(),
Script::new(vec![
Turn::calling("call-0", "summarise the README"),
Turn::saying("the README describes a harness"),
Turn::saying("parent done"),
]),
AllowAll,
)
.await;
let (failed, answer) = run.first_result();
assert!(!failed, "{answer}");
assert_eq!(
answer, "the README describes a harness",
"the subagent's final answer is the tool's result"
);
}
#[tokio::test]
async fn delegated_spend_lands_on_the_budget_that_delegated_it() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = drive(
workspace.path(),
Script::new(vec![
Turn::calling("call-0", "summarise the README").costing(10),
Turn::saying("the README describes a harness").costing(200),
Turn::saying("parent done").costing(10),
])
.with_token_budget(100),
AllowAll,
)
.await;
assert_eq!(
run.stopped_by,
Some(Bound::TokenBudget),
"what the child spent has to be what stops the parent"
);
assert_eq!(
run.requests.all().len(),
2,
"the parent's second round must never have been started"
);
assert_eq!(
run.usage.total_tokens(),
10,
"basis tallies the rounds its own stream carried"
);
}
#[tokio::test]
async fn a_subagent_gets_the_same_one_door() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = drive(
workspace.path(),
Script::new(vec![
Turn::calling("call-0", "summarise the README"),
Turn::saying("child done"),
Turn::saying("parent done"),
]),
AllowAll,
)
.await;
let child = run.requests.roster(1);
assert!(
child.contains(&SPAWN.to_string()),
"a subagent still needs the door: {child:?}"
);
for replaced in ["shell", "background_run", "task"] {
assert!(
!child.contains(&replaced.to_string()),
"{replaced} came back at depth one: {child:?}"
);
}
}
#[tokio::test]
async fn delegation_stops_at_the_floor() {
let workspace = tempfile::tempdir().expect("tempdir");
let run = drive(
workspace.path(),
Script::new(vec![
Turn::calling("call-0", "level one"),
Turn::calling("call-1", "level two"),
Turn::calling("call-2", "level three"),
Turn::saying("deepest done"),
Turn::saying("middle done"),
Turn::saying("parent done"),
]),
AllowAll,
)
.await;
assert!(
run.requests
.any_transcript_contains("goes no deeper than 2"),
"the third level had to be refused, and told why"
);
assert_eq!(
run.asked.len(),
2,
"a call refused by the floor never becomes a question for a person: {:?}",
run.asked
.iter()
.map(|request| request.input.clone())
.collect::<Vec<_>>()
);
}