mod connect;
mod doctor;
mod launch;
use std::path::PathBuf;
use std::process::ExitCode;
use anyhow::{anyhow, Context, Result};
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
use scema_agent::{Agent, Cycle};
use scema_memory::{MemoryKind, Recall};
use scema_policy::render;
use scema_verify::{verify, RecordStore};
use scema_world::{Constraint, Goal};
const DEFAULT_ROOT: &str = ".scema";
#[derive(Parser)]
#[command(
name = "scema",
version,
about = "Scematica Omni — an agent runtime with a world model, counterfactual simulation and verifiable decisions",
long_about = None
)]
struct Cli {
#[arg(long, global = true, default_value = DEFAULT_ROOT)]
root: PathBuf,
#[arg(long, global = true)]
dqstar: Option<String>,
#[arg(long, global = true)]
json: bool,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Observe {
#[arg(default_value = ".")]
locator: String,
},
Simulate {
goal: String,
#[arg(long, default_value = ".")]
path: String,
#[arg(long = "must-not")]
must_not: Vec<String>,
#[arg(long = "ground")]
ground: Vec<String>,
#[arg(long)]
failures: bool,
},
Decide {
goal: String,
#[arg(long, default_value = ".")]
path: String,
#[arg(long = "must-not")]
must_not: Vec<String>,
#[arg(long = "ground")]
ground: Vec<String>,
},
Mission {
goal: String,
#[arg(long, default_value = ".")]
path: String,
#[arg(long = "must-not")]
must_not: Vec<String>,
#[arg(long = "ground")]
ground: Vec<String>,
},
Explain {
id: Option<String>,
#[arg(long)]
list: bool,
},
Verify {
id: Option<String>,
#[arg(long)]
file: Option<PathBuf>,
#[arg(long)]
all: bool,
},
Remember {
#[arg(long)]
stats: bool,
#[arg(long)]
about: Option<String>,
#[arg(long, default_value = "10")]
limit: usize,
},
Policy,
Tui {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
Daemon {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
Mcp {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
Init {
#[arg(long)]
force: bool,
},
Connect {
host: Option<String>,
#[arg(long)]
list: bool,
#[arg(long)]
write: bool,
#[arg(long)]
allow: Option<PathBuf>,
#[arg(long)]
allow_decide: bool,
},
Doctor,
Completions {
shell: Shell,
},
Execute,
Delegate,
Discover,
Pay,
}
fn parse_constraints(specs: &[String]) -> Vec<Constraint> {
specs
.iter()
.filter_map(|s| {
let (subject, detail) = match s.split_once(':') {
Some((a, b)) => (a.trim(), b.trim()),
None => (s.trim(), "declared on the command line"),
};
if subject.is_empty() {
eprintln!("scema: ignoring an empty --must-not (an empty subject would forbid every branch)");
return None;
}
Some(Constraint::must_not(subject, detail))
})
.collect()
}
fn build_goal(statement: &str, must_not: &[String], ground: &[String]) -> Goal {
let mut g = Goal::new("goal", statement);
for c in parse_constraints(must_not) {
g = g.with_constraint(c);
}
for id in ground {
g = g.grounded(id.trim());
}
g
}
fn warn_dangling_grounds(world: &scema_world::WorldState, goal: &Goal) {
for id in &goal.grounded_in {
if !world.signals.iter().any(|s| &s.id == id) {
eprintln!(
"scema: --ground `{id}` names no signal in this world; it will be ignored. Run `scema observe` for the ids."
);
}
}
}
fn print_cycle(c: &Cycle, json: bool, failures: bool) -> Result<()> {
if json {
println!("{}", serde_json::to_string_pretty(&c.record)?);
return Ok(());
}
println!("{}\n", render::world_header(&c.world));
println!("{}\n", render::signals(&c.world));
print!("{}", render::matrix(&c.decision, &c.projections));
println!();
println!("{}\n", render::evaluators(&c.decision));
println!("{}", render::verdict(&c.decision));
if failures {
if let Some(top) = c.decision.ranked.first() {
if let Some(p) = c.projections.iter().find(|p| p.hypothesis == top.hypothesis) {
let text = render::failure_modes(p);
if !text.is_empty() {
println!("\n{text}");
}
}
}
}
match &c.record_path {
Some(p) => println!(
"\nRECORD {} ({})\n {} memory record(s) appended",
c.record.id,
p.display(),
c.remembered
),
None => println!(
"\nRECORD not written — `simulate` is a counterfactual and leaves no trace.\n Run `scema decide` to seal this as {}.",
c.record.id
),
}
Ok(())
}
fn run(cli: Cli) -> Result<ExitCode> {
let agent_for = |persist: bool| {
let mut a = Agent::new(cli.root.clone(), cli.dqstar.clone());
a.persist = persist;
a
};
match &cli.command {
Command::Observe { locator } => {
let agent = agent_for(false);
let w = agent.observe(locator)?;
if cli.json {
println!("{}", serde_json::to_string_pretty(&w)?);
} else {
println!("{}\n", render::world_header(&w));
println!("{}\n", render::signals(&w));
println!("OBJECTS {}", w.objects.len());
for o in w.objects.iter().take(20) {
let attrs: Vec<String> = o
.attrs
.iter()
.map(|(k, v)| format!("{k}={}", v.render()))
.collect();
println!(
" {:<10} {:<24} {}",
o.provenance.label(),
o.label,
if attrs.is_empty() {
"(no values — unseen, not empty)".to_string()
} else {
attrs.join(" ")
}
);
}
if w.objects.len() > 20 {
println!(" … {} more", w.objects.len() - 20);
}
}
Ok(ExitCode::SUCCESS)
}
Command::Simulate { goal, path, must_not, ground, failures } => {
let agent = agent_for(false);
let world = agent.observe(path)?;
let goal = build_goal(goal, must_not, ground);
warn_dangling_grounds(&world, &goal);
let c = agent.cycle_over(world, goal)?;
print_cycle(&c, cli.json, *failures)?;
Ok(ExitCode::SUCCESS)
}
Command::Decide { goal, path, must_not, ground }
| Command::Mission { goal, path, must_not, ground } => {
let agent = agent_for(true);
let world = agent.observe(path)?;
let goal = build_goal(goal, must_not, ground);
warn_dangling_grounds(&world, &goal);
let c = agent.cycle_over(world, goal)?;
let narrate = matches!(cli.command, Command::Mission { .. });
print_cycle(&c, cli.json, narrate)?;
Ok(ExitCode::SUCCESS)
}
Command::Explain { id, list } => {
let store = RecordStore::new(cli.root.clone());
if *list || id.is_none() {
let ids = store.ids()?;
if ids.is_empty() {
println!("No decision records under {}.", cli.root.display());
println!("Run `scema decide \"<goal>\"` to seal one.");
return Ok(ExitCode::SUCCESS);
}
println!("{} record(s), newest first:", ids.len());
for id in ids {
match store.load(&id) {
Ok(r) => println!(
" {} {:<40} {}",
r.id,
{
let s = r.goal.statement.clone();
if s.chars().count() > 40 {
s.chars().take(39).collect::<String>() + "…"
} else {
s
}
},
match (&r.decision.chosen, &r.decision.abstention) {
(Some(c), _) => format!("chose {c}"),
(None, Some(a)) => format!("abstained — {}", a.headline()),
_ => "—".into(),
}
),
Err(e) => println!(" {id} <unreadable: {e}>"),
}
}
return Ok(ExitCode::SUCCESS);
}
let record = store.load(id.as_ref().unwrap())?;
if cli.json {
println!("{}", serde_json::to_string_pretty(&record)?);
return Ok(ExitCode::SUCCESS);
}
println!("RECORD {} runtime {}", record.id, record.runtime);
println!("GOAL {}", record.goal.statement);
for c in &record.goal.constraints {
println!(" constraint {:?} `{}` — {}", c.kind, c.subject, c.detail);
}
println!();
println!("{}\n", render::world_header(&record.world));
print!("{}", render::matrix(&record.decision, &record.projections));
println!();
println!("{}\n", render::evaluators(&record.decision));
println!("{}", render::verdict(&record.decision));
let v = verify(&record);
println!(
"\nCOMMITMENT {}\n root {}",
if v.valid { "VALID — the record matches its commitment" } else { "INVALID" },
record.commitment.root
);
Ok(ExitCode::SUCCESS)
}
Command::Verify { id, file, all } => {
let store = RecordStore::new(cli.root.clone());
let records = if let Some(f) = file {
vec![RecordStore::load_path(f).with_context(|| format!("reading {}", f.display()))?]
} else if *all {
store.ids()?.iter().filter_map(|i| store.load(i).ok()).collect()
} else {
let id = id
.as_ref()
.ok_or_else(|| anyhow!("give a record id, --file, or --all"))?;
vec![store.load(id)?]
};
if records.is_empty() {
println!("Nothing to verify under {}.", cli.root.display());
return Ok(ExitCode::SUCCESS);
}
let results: Vec<_> = records.iter().map(verify).collect();
if cli.json {
println!("{}", serde_json::to_string_pretty(&results)?);
} else {
for v in &results {
println!("{} {}", v.id, if v.valid { "VALID" } else { "INVALID" });
for m in &v.mismatches {
println!(
" {:<12} committed {}… recomputed {}…",
m.field,
&m.committed[..m.committed.len().min(12)],
&m.recomputed[..m.recomputed.len().min(12)]
);
}
if v.root_only {
println!(" every part verifies but the root does not — the root was edited on its own");
}
}
println!(
"\nThis proves the record was not edited after sealing. It does NOT prove the\nworld was as described — provenance carries that, not the digest."
);
}
if results.iter().all(|v| v.valid) {
Ok(ExitCode::SUCCESS)
} else {
Ok(ExitCode::FAILURE)
}
}
Command::Remember { stats, about, limit } => {
let agent = agent_for(false);
let mem = agent.memory();
if *stats || about.is_none() {
let counts = mem.counts()?;
println!("MEMORY {}", mem.root().join("memory").display());
for (kind, n, corrupt) in counts {
println!(
" {:<16} {:>6} record(s){}",
format!("{kind:?}"),
n,
if corrupt > 0 { format!(" {corrupt} unreadable line(s)") } else { String::new() }
);
}
let c = mem.calibration()?;
println!("\nCALIBRATION");
println!(" branches not taken, recorded {}", c.recorded);
println!(" of those, later resolved {}", c.resolved);
println!(" unresolved {}", c.unresolved);
match c.mean_abs_error {
Some(e) => println!(" mean |projected − realised| {e:.3}"),
None => println!(
" mean |projected − realised| — (nothing resolved; a branch nobody ran has no outcome)"
),
}
return Ok(ExitCode::SUCCESS);
}
let query = Recall {
subject: about.clone(),
limit: Some(*limit),
..Default::default()
};
for kind in MemoryKind::all() {
let hits = mem.recall(kind, &query)?;
if hits.is_empty() {
continue;
}
println!("{kind:?}");
for h in hits {
println!(" {} {} {}", h.id, h.subject, serde_json::to_string(&h.body)?);
}
}
Ok(ExitCode::SUCCESS)
}
Command::Policy => {
let agent = agent_for(false);
let w = agent.config.weights;
println!("UTILITY U = R − λ₁K − λ₂C − λ₃U + λ₄V");
println!(" λ₁ risk {:.2}", w.risk);
println!(" λ₂ cost {:.2}", w.cost);
println!(" λ₃ uncertainty {:.2}", w.uncertainty);
println!(" λ₄ reversibility {:.2}", w.reversibility);
println!("\n These are a stated preference, not a fitted parameter. They are hashed");
println!(" into every record so a ranking can be re-read against them later.");
println!("\nGATES");
println!(" min measured fraction {:.0}%", agent.config.min_coverage * 100.0);
println!(" specialist veto at ≤ {:.2}", agent.config.veto_at_or_below);
println!("\nOBSERVERS");
for o in agent.observers() {
println!(" {:<10} {}", o.name(), o.about());
}
println!("\nEVALUATORS");
for e in agent.evaluators() {
println!(" {:<10} {}", e.name(), e.about());
}
Ok(ExitCode::SUCCESS)
}
Command::Tui { args } => launch::run(launch::TUI, args),
Command::Daemon { args } => launch::run(launch::DAEMON, args),
Command::Mcp { args } => launch::run(launch::MCP, args),
Command::Init { force } => {
let root = &cli.root;
std::fs::create_dir_all(root.join("decisions"))
.with_context(|| format!("creating {}", root.join("decisions").display()))?;
std::fs::create_dir_all(root.join("memory"))
.with_context(|| format!("creating {}", root.join("memory").display()))?;
let ignore = root.join(".gitignore");
if !ignore.exists() || *force {
std::fs::write(
&ignore,
"# Machine-local. Decision records cite absolute paths and memory is a\n\
# per-checkout history; neither is meaningful in somebody else's clone.\n\
*\n",
)
.with_context(|| format!("writing {}", ignore.display()))?;
}
println!("Initialised {}", root.display());
println!(" decisions/ sealed decision records, one JSON file each");
println!(" memory/ four append-only JSONL logs");
println!(" .gitignore this directory is machine-local");
println!();
println!("Nothing has been decided yet. Start with:");
println!(" scema observe . # what is out there");
println!(" scema simulate \"<goal>\" --ground <id> # rank branches, write nothing");
println!(" scema tui # the same thing, interactively");
Ok(ExitCode::SUCCESS)
}
Command::Connect { host, list, write, allow, allow_decide } => {
if *list || host.is_none() {
println!("Assistants this can wire up:\n");
for (key, h) in connect::catalogue() {
println!(
" {:<15} {:<32} {}",
key,
h.label,
match h.scope {
connect::Scope::Project => format!("project: {}", h.project_path),
connect::Scope::User =>
"user-level (printed, never written)".to_string(),
}
);
}
println!("\n scema connect <host> print the snippet and where it goes");
println!(" scema connect <host> --write merge it, project-local hosts only");
return Ok(ExitCode::SUCCESS);
}
let key = host.as_deref().unwrap();
let h = connect::host(key).ok_or_else(|| {
anyhow!(
"unknown host `{key}`. Known: {}",
connect::catalogue().keys().cloned().collect::<Vec<_>>().join(", ")
)
})?;
let project = doctor::cwd();
let allow_path = allow.clone().unwrap_or_else(|| project.clone());
let text = connect::snippet(h, &allow_path, *allow_decide)?;
if *write {
match connect::write(h, &project, &allow_path, *allow_decide) {
Ok(connect::Written::Created(p)) => println!("created {}", p.display()),
Ok(connect::Written::Merged(p)) => {
println!("merged the `scema` entry into {} (nothing else touched)", p.display())
}
Ok(connect::Written::Unchanged(p)) => {
println!("{} already has this exact entry", p.display())
}
Err(e) => {
eprintln!("scema connect: {e:#}\n");
println!("{text}");
return Ok(ExitCode::from(2));
}
}
} else {
match h.scope {
connect::Scope::Project => println!("{} — {}\n", h.label, h.project_path),
connect::Scope::User => println!("{}\n{}\n", h.label, h.user_hint),
}
println!("{text}");
}
println!("Then: {}", h.after);
if !*allow_decide {
println!(
"\nNote: `omni_decide` is not advertised to the model. The server can perceive,\n\
simulate, explain and verify; it cannot seal a record. Add --allow-decide if\n\
you want that, having decided you want it."
);
}
Ok(ExitCode::SUCCESS)
}
Command::Doctor => {
let project = doctor::cwd();
let findings = doctor::run(&cli.root, &project);
if cli.json {
let rows: Vec<_> = findings
.iter()
.map(|f| {
serde_json::json!({
"verdict": format!("{:?}", f.verdict).to_lowercase(),
"check": f.check,
"detail": f.detail,
"fix": f.fix,
})
})
.collect();
println!("{}", serde_json::to_string_pretty(&rows)?);
} else {
println!("scema doctor — {}\n", scema_agent::RUNTIME);
for f in &findings {
println!(" [{}] {:<24} {}", f.verdict.glyph(), f.check, f.detail);
if !f.fix.is_empty() {
println!(" {:<24} → {}", "", f.fix);
}
}
println!("\nThis command changes nothing. Every finding names the fix and stops there.");
}
Ok(match doctor::worst(&findings) {
doctor::Verdict::Fail => ExitCode::FAILURE,
_ => ExitCode::SUCCESS,
})
}
Command::Completions { shell } => {
let mut cmd = Cli::command();
let name = cmd.get_name().to_string();
clap_complete::generate(*shell, &mut cmd, name, &mut std::io::stdout());
Ok(ExitCode::SUCCESS)
}
Command::Execute => not_built(
"execute",
"Nothing in this workspace writes to an environment it observed. An action path \
needs the approval model from `alchem-link` — risk declared per tool, no \
terminal means deny, secrets refused before the prompt — wired in front of it.",
),
Command::Delegate => not_built(
"delegate",
"Agent-to-agent hiring runs over the ScemaDEX relay and needs a bonded result \
format, so a specialist that answers badly can be slashed rather than merely \
disbelieved.",
),
Command::Discover => not_built(
"discover",
"Capability discovery needs the relay's catalogue endpoint and a policy for \
which capabilities this agent is allowed to want.",
),
Command::Pay => not_built(
"pay",
"x402 settlement exists in `scematica-protocol`, but paying on the agent's own \
initiative needs a spend policy first. A runtime that can spend without one is \
a runtime nobody should install.",
),
}
}
fn not_built(verb: &str, why: &str) -> Result<ExitCode> {
eprintln!("scema {verb}: not built yet.\n");
eprintln!(" {why}");
eprintln!("\n It is listed in `--help` on purpose: the shape of this runtime includes");
eprintln!(" this verb, and finding that out from the tool beats finding it out later.");
Ok(ExitCode::from(2))
}
fn main() -> ExitCode {
let cli = Cli::parse();
match run(cli) {
Ok(code) => code,
Err(e) => {
eprintln!("scema: {e:#}");
ExitCode::FAILURE
}
}
}