use firstpass_core::RoutingMode;
use firstpass_proxy::calibrate::{calibrate_from_store, calibrate_from_store_ltt};
use firstpass_proxy::ope::{CandidatePolicy, ips_from_store, ope_from_store};
use firstpass_proxy::{ProxyConfig, cli, run, store};
const HELP: &str = "\
firstpass — the cheapest model that provably passes, with a receipt for every call.
USAGE:
firstpass upgrade upgrade in place, or print the exact command for however
this copy was installed (brew / uv / npm / docker / cargo)
firstpass demo no keys, no config: run one real gated decision and print
its receipt (local mock upstream, real proxy)
firstpass onboard [--apply] agentic setup: ask three questions, write firstpass.toml,
start the proxy, route your agent, verify
[--provider anthropic|openai|google|local] [--shape json|code|prose|mixed]
[--mode observe|enforce] answer non-interactively (an agent piping input never blocks)
[--yes] [--no-config] take the defaults / skip config generation entirely
firstpass offboard undo it: strip the rc line, stop the proxy, print the unset
firstpass up start the proxy (serves until Ctrl-C)
firstpass doctor validate config, provider key, and gate binaries
firstpass trace [--limit N] print recent audit traces as JSON lines (default 20)
firstpass savings [--json] spend vs the always-top counterfactual, from your own receipts
firstpass evals [--json] per-gate verdict rates + escalation + serve-by-rung, from receipts
firstpass predictor-eval [--json] [--lr L] [--l2 R]
prequential AUC/Brier of the per-query P(pass) predictor
firstpass explain <trace-id> [--json] why one routing decision went the way it did
firstpass export [--out F] write the sealed receipt log as JSONL (hand to an auditor)
firstpass verify [--file F] [--json]
independently re-derive the receipt hash chain; exit 1 if broken
firstpass calibrate [--alpha A] [--delta D] [--min-n N] [--method conformal|ltt]
recalibrate the serving threshold from deferred feedback
(default method: conformal; ltt = Learn-then-Test / RCPS)
firstpass ope --config <candidate.toml> [--db <path>] [--tenant <id>]
evaluate a candidate policy against logged traffic before enforcing
firstpass ope --start-rung N [--db <path>] [--tenant <id>]
IPS/SNIPS/DR estimate for a fixed start-rung (requires propensity-logged traffic)
firstpass modes list routing-mode profiles, what each sets, and honest tradeoffs
firstpass mcp serve MCP over stdio (agent reads traces + submits feedback)
firstpass --help | --version
ENVIRONMENT (shared by every subcommand):
FIRSTPASS_MODE=observe|enforce FIRSTPASS_BIND=127.0.0.1:8080
FIRSTPASS_MODE_PROFILE=balanced|... global routing-mode preset (default: balanced)
FIRSTPASS_CONFIG=./firstpass.toml (or FIRSTPASS_CONFIG_TOML=<inline>)
FIRSTPASS_DB=./firstpass.db RUST_LOG=info
Point your agent at it: export ANTHROPIC_BASE_URL=http://127.0.0.1:8080
Offboard any time: unset ANTHROPIC_BASE_URL";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
match args.get(1).map(String::as_str).unwrap_or("--help") {
"up" => {
run::init_tracing();
run::serve(ProxyConfig::from_env()?).await
}
"demo" => firstpass_proxy::demo::run().await,
"upgrade" => cmd_upgrade(),
"onboard" => cmd_onboard(args.iter().any(|a| a == "--apply")),
"offboard" => cmd_offboard(),
"doctor" => cmd_doctor(),
"trace" => cmd_trace(&args),
"savings" => cmd_savings(&args),
"evals" => cmd_evals(&args),
"predictor-eval" => cmd_predictor_eval(&args),
"explain" => cmd_explain(&args),
"export" => cmd_export(&args),
"verify" => cmd_verify(&args),
"calibrate" => cmd_calibrate(&args),
"ope" => cmd_ope(&args),
"modes" => cmd_modes(),
"mcp" => {
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(&args, &config);
let db = config.db_path;
tokio::task::spawn_blocking(move || firstpass_proxy::mcp::serve_stdio(&db, &tenant))
.await??;
Ok(())
}
"--help" | "-h" | "help" => {
println!("{HELP}");
Ok(())
}
"--version" | "-V" => {
println!("firstpass {}", env!("CARGO_PKG_VERSION"));
Ok(())
}
other => {
eprintln!("firstpass: unknown command `{other}`\n\n{HELP}");
std::process::exit(2);
}
}
}
fn cmd_modes() -> Result<(), Box<dyn std::error::Error>> {
println!(
"Routing-mode profiles — set via x-firstpass-mode header, \
route `routing_mode`, or FIRSTPASS_MODE_PROFILE env var.\n"
);
for mode in RoutingMode::ALL {
let p = mode.preset();
println!(" {:8} {}", mode.as_str(), p.description);
println!(" tradeoff: {}", p.tradeoff);
let mut overrides = Vec::new();
if let Some(s) = p.speculation {
overrides.push(format!("speculation={s}"));
}
if let Some(d) = p.max_rungs_delta {
overrides.push(format!("max_rungs+={d}"));
}
if p.start_at_top {
overrides.push("start_at_top=true".to_owned());
}
if !overrides.is_empty() {
println!(" sets: {}", overrides.join(", "));
}
println!();
}
Ok(())
}
fn cmd_doctor() -> Result<(), Box<dyn std::error::Error>> {
match ProxyConfig::from_env() {
Ok(config) => {
let report = cli::doctor(&config, |k| std::env::var(k).ok());
print!("{}", report.render());
if report.healthy() {
Ok(())
} else {
std::process::exit(1);
}
}
Err(e) => {
println!("✗ config: {e}\n\nnot healthy — fix the config error above.");
std::process::exit(1);
}
}
}
fn cmd_upgrade() -> Result<(), Box<dyn std::error::Error>> {
let exe = std::env::current_exe().unwrap_or_default();
let exe = std::fs::canonicalize(&exe).unwrap_or(exe);
let in_container = std::path::Path::new("/.dockerenv").exists();
let path_var = std::env::var("PATH").ok();
let updater_present = exe
.parent()
.is_some_and(|d| d.join("firstpass-update").is_file())
|| cli::command_on_path("firstpass-update", path_var.as_deref());
let channel = cli::detect_channel(&exe, updater_present, in_container);
print!(
"{}",
cli::upgrade_report(channel, env!("CARGO_PKG_VERSION"))
);
if channel == cli::Channel::Dist {
let updater = exe
.parent()
.map(|d| d.join("firstpass-update"))
.filter(|p| p.is_file())
.unwrap_or_else(|| "firstpass-update".into());
let status = std::process::Command::new(updater).status()?;
if !status.success() {
eprintln!("\nupdater exited with {status} — see the output above");
std::process::exit(status.code().unwrap_or(1));
}
}
Ok(())
}
fn ask<T: Copy>(prompt: &str, opts: &[(T, &str, &str)], default_ix: usize) -> T {
use std::io::Write as _;
println!("\n{prompt}");
for (i, (_, id, blurb)) in opts.iter().enumerate() {
let mark = if i == default_ix { "*" } else { " " };
println!(" {mark} {}) {id:<9} {blurb}", i + 1);
}
print!(
" choice [1-{}, enter = {}]: ",
opts.len(),
opts[default_ix].1
);
let _ = std::io::stdout().flush();
let mut line = String::new();
if std::io::stdin().read_line(&mut line).is_err() {
return opts[default_ix].0;
}
let line = line.trim();
if line.is_empty() {
return opts[default_ix].0;
}
line.parse::<usize>()
.ok()
.filter(|n| (1..=opts.len()).contains(n))
.map_or_else(
|| {
opts.iter()
.find(|(_, id, _)| *id == line)
.map_or(opts[default_ix].0, |o| o.0)
},
|n| opts[n - 1].0,
)
}
fn resolve_choice(
args: &[String],
interactive: bool,
) -> (firstpass_proxy::onboard::LadderChoice, bool) {
use firstpass_core::Mode;
use firstpass_proxy::onboard::{LadderChoice, Provider, Shape};
let flag = |name: &str| -> Option<String> {
args.iter()
.position(|a| a == name)
.and_then(|i| args.get(i + 1))
.cloned()
};
let d = LadderChoice::default();
let provider = flag("--provider")
.and_then(|v| Provider::ALL.into_iter().find(|p| p.id() == v))
.unwrap_or(d.provider);
let shape = flag("--shape")
.and_then(|v| Shape::ALL.into_iter().find(|s| s.id() == v))
.unwrap_or(d.shape);
let mode = match flag("--mode").as_deref() {
Some("enforce") => Mode::Enforce,
Some("observe") => Mode::Observe,
_ => d.mode,
};
let all_given =
flag("--provider").is_some() && flag("--shape").is_some() && flag("--mode").is_some();
if !interactive || all_given || args.iter().any(|a| a == "--yes" || a == "-y") {
return (
LadderChoice {
provider,
shape,
mode,
},
false,
);
}
println!("\nThree questions and onboarding writes your firstpass.toml.");
let provider = ask(
"1/3 · Which provider should the ladder open on?",
&Provider::ALL.map(|p| (p, p.id(), p.blurb())),
0,
);
let shape = ask(
"2/3 · What do these requests produce? (this picks the gate)",
&Shape::ALL.map(|s| (s, s.id(), s.blurb())),
0,
);
let mode = ask(
"3/3 · Start in which mode?",
&[
(
Mode::Observe,
"observe",
"forward unchanged, just collect receipts (recommended)",
),
(
Mode::Enforce,
"enforce",
"serve from the cheap rung once a gate passes",
),
],
0,
);
(
LadderChoice {
provider,
shape,
mode,
},
true,
)
}
fn cmd_onboard(apply: bool) -> Result<(), Box<dyn std::error::Error>> {
use firstpass_proxy::onboard;
let env = onboard::detect(
|k| std::env::var(k).ok(),
|bin| {
std::env::var("PATH")
.is_ok_and(|p| std::env::split_paths(&p).any(|d| d.join(bin).is_file()))
},
|| {
let bind = std::env::var("FIRSTPASS_BIND").unwrap_or_else(|_| "127.0.0.1:8080".into());
std::net::TcpStream::connect_timeout(
&bind
.parse()
.unwrap_or_else(|_| ([127, 0, 0, 1], 8080).into()),
std::time::Duration::from_millis(400),
)
.is_ok()
},
);
let home = std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".into()));
let (rc, _) = onboard::shell_wiring(&env.shell, &home, &env.bind);
let args: Vec<String> = std::env::args().collect();
let cfg_path = std::path::PathBuf::from("firstpass.toml");
let config = if args.iter().any(|a| a == "--no-config") {
None
} else if cfg_path.exists() {
println!("firstpass.toml already present — leaving it alone (--no-config to silence).\n");
None
} else {
let interactive = std::io::IsTerminal::is_terminal(&std::io::stdin());
let (choice, asked) = resolve_choice(&args, interactive);
if !asked {
println!(
"config: provider={} shape={} mode={} \
(set with --provider/--shape/--mode; asked interactively on a terminal)\n",
choice.provider.id(),
choice.shape.id(),
if choice.mode == firstpass_core::Mode::Enforce {
"enforce"
} else {
"observe"
},
);
}
Some(onboard::ConfigPlan {
path: cfg_path,
choice,
})
};
let steps = onboard::plan(&env, &home, onboard::rc_wired(&rc), config.as_ref());
print!("{}", onboard::render(&env, &steps, apply));
if apply {
print!("\n{}", onboard::execute(&env, &steps)?);
}
if let Some(c) = &config
&& c.choice.shape == firstpass_proxy::onboard::Shape::Code
{
println!(
"\nnext: the `unit-tests` gate is a placeholder. Point its `cmd` at a wrapper that \
reads\n the candidate as JSON on stdin and prints a verdict on stdout, then run \
`firstpass doctor`."
);
}
Ok(())
}
fn cmd_offboard() -> Result<(), Box<dyn std::error::Error>> {
let home = std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".into()));
print!("{}", firstpass_proxy::onboard::offboard(&home)?);
Ok(())
}
fn tenant_arg(args: &[String], config: &ProxyConfig) -> String {
args.iter()
.position(|a| a == "--tenant")
.and_then(|i| args.get(i + 1))
.cloned()
.unwrap_or_else(|| config.tenant_id.clone())
}
fn cmd_trace(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let limit = args
.iter()
.position(|a| a == "--limit")
.and_then(|i| args.get(i + 1))
.and_then(|n| n.parse::<usize>().ok())
.unwrap_or(20);
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
let traces = store::load_tenant_traces(std::path::Path::new(&config.db_path), &tenant)
.unwrap_or_default();
println!("{}", cli::format_traces(&traces, limit));
Ok(())
}
fn cmd_savings(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
let traces = store::load_tenant_traces(std::path::Path::new(&config.db_path), &tenant)
.unwrap_or_default();
let summary = cli::summarize_savings(&traces);
if args.iter().any(|a| a == "--json") {
println!("{}", serde_json::to_string_pretty(&summary)?);
} else {
println!("{}", cli::format_savings(&summary));
}
Ok(())
}
fn cmd_evals(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
let traces = store::load_tenant_traces(std::path::Path::new(&config.db_path), &tenant)
.unwrap_or_default();
let summary = cli::summarize_evals(&traces);
if args.iter().any(|a| a == "--json") {
println!("{}", serde_json::to_string_pretty(&summary)?);
} else {
println!("{}", cli::format_evals(&summary));
}
Ok(())
}
fn cmd_predictor_eval(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
let flag = |name: &str, default: f64| -> f64 {
args.iter()
.position(|a| a == name)
.and_then(|i| args.get(i + 1))
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(default)
};
let (lr, l2) = (flag("--lr", 0.05), flag("--l2", 1e-4));
let traces = store::load_tenant_traces(std::path::Path::new(&config.db_path), &tenant)
.unwrap_or_default();
let eval = cli::evaluate_predictor(&traces, lr, l2);
if args.iter().any(|a| a == "--json") {
println!("{}", serde_json::to_string_pretty(&eval)?);
} else {
let auc = eval
.auc
.map_or("n/a (one class)".to_owned(), |a| format!("{a:.3}"));
println!(
"predictor-eval: n={} auc={} brier={:.4} (lr={lr} l2={l2})\n AUC 0.5=no signal, >0.65 usable; Brier 0.25=coin-flip baseline, lower is better",
eval.n, auc, eval.brier
);
}
Ok(())
}
fn cmd_explain(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let trace_id = args
.get(2)
.filter(|a| !a.starts_with("--"))
.ok_or("usage: firstpass explain <trace-id> [--json]")?;
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
match store::load_trace_view(std::path::Path::new(&config.db_path), &tenant, trace_id)? {
Some(trace) => {
let ex = cli::explain_trace(&trace);
if args.iter().any(|a| a == "--json") {
println!("{}", serde_json::to_string_pretty(&ex)?);
} else {
println!("{}", ex.summary);
for a in &ex.attempts {
let gates: Vec<String> =
a.gates.iter().map(|(g, v)| format!("{g}={v}")).collect();
println!(
" rung {} {} → {} [{}]",
a.rung,
a.model,
a.verdict,
gates.join(", ")
);
}
}
Ok(())
}
None => {
eprintln!("unknown trace_id {trace_id:?}");
std::process::exit(1);
}
}
}
fn cmd_export(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let config = ProxyConfig::from_env()?;
let traces = store::load_all_traces(std::path::Path::new(&config.db_path)).unwrap_or_default();
let jsonl = cli::export_receipts_jsonl(&traces);
match args
.iter()
.position(|a| a == "--out")
.and_then(|i| args.get(i + 1))
{
Some(path) => {
std::fs::write(path, jsonl)?;
eprintln!("exported {} receipts to {path}", traces.len());
}
None => print!("{jsonl}"),
}
Ok(())
}
fn cmd_verify(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let traces = match args
.iter()
.position(|a| a == "--file")
.and_then(|i| args.get(i + 1))
{
Some(path) => {
let text = std::fs::read_to_string(path)?;
cli::parse_receipt_jsonl(&text).map_err(|e| format!("{path}: {e}"))?
}
None => {
let config = ProxyConfig::from_env()?;
store::load_all_traces(std::path::Path::new(&config.db_path)).unwrap_or_default()
}
};
let report = cli::verify_receipts(&traces);
if args.iter().any(|a| a == "--json") {
println!("{}", serde_json::to_string_pretty(&report)?);
} else if report.valid {
println!(
"OK: {} receipts, hash chain intact from genesis",
report.receipts
);
} else {
println!(
"TAMPERED: chain broke at receipt {} — {}",
report.broken_at.map_or("?".to_owned(), |i| i.to_string()),
report.detail.as_deref().unwrap_or("unknown")
);
}
if !report.valid {
std::process::exit(1);
}
Ok(())
}
fn cmd_calibrate(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let flag = |name: &str, default: f64| -> f64 {
args.iter()
.position(|a| a == name)
.and_then(|i| args.get(i + 1))
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(default)
};
let alpha = flag("--alpha", 0.1);
let delta = flag("--delta", 0.05);
let min_n = flag("--min-n", 30.0) as usize;
let method = args
.iter()
.position(|a| a == "--method")
.and_then(|i| args.get(i + 1))
.map(String::as_str)
.unwrap_or("conformal");
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
match method {
"ltt" => {
let report = calibrate_from_store_ltt(&config.db_path, &tenant, alpha, delta, min_n)?;
print!("{}", report.render());
}
_ => {
let report = calibrate_from_store(&config.db_path, &tenant, alpha, delta, min_n)?;
print!("{}", report.render());
}
}
Ok(())
}
fn cmd_ope(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let env_config = ProxyConfig::from_env()?;
let db_path = args
.iter()
.position(|a| a == "--db")
.and_then(|i| args.get(i + 1))
.cloned()
.unwrap_or_else(|| env_config.db_path.clone());
let tenant = tenant_arg(args, &env_config);
if let Some(n_str) = args
.iter()
.position(|a| a == "--start-rung")
.and_then(|i| args.get(i + 1))
{
let start_rung: u32 = n_str.parse().map_err(|e| {
format!("firstpass ope: --start-rung must be a non-negative integer: {e}")
})?;
let report = ips_from_store(&db_path, &tenant, start_rung)?;
print!("{}", report.render());
return Ok(());
}
let config_path = args
.iter()
.position(|a| a == "--config")
.and_then(|i| args.get(i + 1))
.ok_or_else(|| {
eprintln!(
"firstpass ope: one of --config <candidate.toml> or --start-rung N is required"
);
std::process::exit(1);
#[allow(unreachable_code)]
"unreachable"
})?;
let toml = std::fs::read_to_string(config_path)
.map_err(|e| format!("firstpass ope: cannot read --config {config_path:?}: {e}"))?;
let policy = CandidatePolicy::from_toml(&toml)
.map_err(|e| format!("firstpass ope: invalid candidate config: {e}"))?;
let report = ope_from_store(&db_path, &tenant, &policy)?;
print!("{}", report.render());
Ok(())
}