use clap::ArgMatches;
use std::path::Path;
use std::sync::Arc;
pub async fn run(matches: &ArgMatches) {
let file = matches
.get_one::<String>("agent")
.expect("agent argument is required");
let input = matches
.get_one::<String>("input")
.cloned()
.unwrap_or_else(|| "{}".to_string());
let max_iterations = matches
.get_one::<String>("max-iterations")
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(10);
let agent_path = resolve_agent_path(file);
let dsl_source = match std::fs::read_to_string(&agent_path) {
Ok(s) => s,
Err(e) => {
eprintln!("✗ Failed to read '{}': {}", agent_path.display(), e);
std::process::exit(1);
}
};
let agent_name = agent_path
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "agent".to_string());
let description = match dsl::parse_dsl(&dsl_source) {
Ok(tree) => {
let meta = dsl::extract_metadata(&tree, &dsl_source);
meta.get("description").cloned().unwrap_or_default()
}
Err(_) => String::new(),
};
let provider =
match symbi_runtime::reasoning::providers::cloud::CloudInferenceProvider::from_env() {
Some(p) => {
Arc::new(p) as Arc<dyn symbi_runtime::reasoning::inference::InferenceProvider>
}
None => {
eprintln!("✗ No LLM provider configured.");
eprintln!(" Set one of: OPENROUTER_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY");
std::process::exit(1);
}
};
println!("→ Running agent: {} ({})", agent_name, agent_path.display());
if !description.is_empty() {
println!(" {}", description);
}
println!("→ Input: {}", truncate(&input, 200));
println!();
use symbi_runtime::reasoning::circuit_breaker::CircuitBreakerRegistry;
use symbi_runtime::reasoning::context_manager::DefaultContextManager;
use symbi_runtime::reasoning::conversation::{Conversation, ConversationMessage};
use symbi_runtime::reasoning::executor::DefaultActionExecutor;
use symbi_runtime::reasoning::loop_types::{BufferedJournal, LoopConfig};
use symbi_runtime::reasoning::policy_bridge::DefaultPolicyGate;
use symbi_runtime::reasoning::reasoning_loop::ReasoningLoopRunner;
use symbi_runtime::types::AgentId;
let insecure_allow_all = std::env::var("SYMBI_INSECURE_ALLOW_ALL").as_deref() == Ok("1");
let policy_gate: Arc<dyn symbi_runtime::reasoning::policy_bridge::ReasoningPolicyGate> =
if insecure_allow_all {
eprintln!("\n");
eprintln!("================================================================");
eprintln!("WARNING: SYMBI_INSECURE_ALLOW_ALL=1 is set");
eprintln!("Policy gate is in PERMISSIVE mode for this `symbi run` invocation.");
eprintln!("Every LLM-proposed tool call and delegation will be allowed.");
eprintln!("This is only safe for local development. Do NOT use in production.");
eprintln!("================================================================\n");
Arc::new(DefaultPolicyGate::permissive_for_dev_only())
} else if let Some(cedar_gate) = super::up::try_wire_cedar_policy_gate().await {
cedar_gate
} else {
tracing::info!(
"policy gate: fail-closed default (no policies/*.cedar found); configure CedarPolicyGate, OpaPolicyGateBridge, or another ReasoningPolicyGate"
);
Arc::new(DefaultPolicyGate::new())
};
let runner = ReasoningLoopRunner {
provider,
policy_gate,
executor: Arc::new(DefaultActionExecutor::default()),
context_manager: Arc::new(DefaultContextManager::default()),
circuit_breakers: Arc::new(CircuitBreakerRegistry::default()),
journal: Arc::new(BufferedJournal::new(1000)),
knowledge_bridge: None,
};
let system_prompt = format!(
"You are agent '{}'. Follow the governance rules defined in your DSL.\n\n--- Agent DSL ---\n{}\n--- End DSL ---",
agent_name, dsl_source
);
let mut conv = Conversation::with_system(&system_prompt);
conv.push(ConversationMessage::user(&input));
let config = LoopConfig {
max_iterations,
max_total_tokens: 100_000,
..Default::default()
};
let result = runner.run(AgentId::new(), conv, config).await;
println!("{}", result.output);
eprintln!(
"\n--- {} iterations, {} tokens, terminated: {:?} ---",
result.iterations, result.total_usage.total_tokens, result.termination_reason
);
}
fn resolve_agent_path(name: &str) -> std::path::PathBuf {
let path = Path::new(name);
if path.exists() {
return path.to_path_buf();
}
let already_extended = name.ends_with(".symbi") || name.ends_with(".dsl");
let candidate_exts: &[&str] = if already_extended {
&[]
} else {
&["symbi", "dsl"]
};
for ext in candidate_exts {
let with_ext = format!("{}.{}", name, ext);
let path_ext = Path::new(&with_ext);
if path_ext.exists() {
return path_ext.to_path_buf();
}
}
let agents_path = Path::new("agents").join(name);
if agents_path.exists() {
return agents_path;
}
for ext in candidate_exts {
let agents_path_ext = Path::new("agents").join(format!("{}.{}", name, ext));
if agents_path_ext.exists() {
return agents_path_ext;
}
}
path.to_path_buf()
}
fn truncate(s: &str, max: usize) -> &str {
if s.len() <= max {
s
} else {
&s[..max]
}
}