A runtime where code decides and models only propose

solvi is a decision runtime built on a simple split: plain Python functions and checks describe what to compute, while a model — whether an LLM, a custom classifier, or a small local network — only proposes answers where judgement is needed. solvi's checks then decide. Every answer ships with a confidence, a verifiable reason (the rule's inputs, a formula over computed facts, or a quote with character offsets in the source), and a hash-chained trace that can be re-executed later to confirm the result or pinpoint the exact step that changed. When a value cannot be computed or checked, solvi abstains or hands the case to a person instead of guessing; a failed hard check always overrides any model confidence.

Two layers: ready systems and replaceable building blocks

solvi 1.0 separates ready-to-configure systems from the building blocks they are made of. The ready systems are solvi.build (decisions from labelled examples with a promise on errors), solvi.Agent (acting in an environment), solvi.Guard (checking an agent's tool calls), and solvi.Knowledge (what a system learned and from whom). Every building block in solvi.core is replaceable by a custom part. Anything that works but has not shown measured gain yet lives in solvi.experimental.

Installation tiers and Python requirement

The core installs with pip install solvi and brings numpy, scipy, and pydantic. Optional extras add capability stacks: "solvi[model]" adds torch and transformers for ModernBERT field extractors and the decider; "solvi[onnx]" adds onnxruntime and tokenizers for the CPU-only decider; "solvi[serve]" adds fastapi and uvicorn for HTTP endpoints; "solvi[mcp]" adds the official MCP SDK; "solvi[duckdb]" adds DuckDB storage (with a postgres variant); "solvi[lora]" adds peft for experimental per-question LoRA adapters. The runtime requires Python 3.11 or newer (tested through 3.14).

Typed questions, decider backends, and hard overrides

Questions are declared through pydantic model fields. The field type defines the answer kind — single choice, multi-choice, ordered score, yes/no. A decider answers each question about a text or JSON/pydantic state with probabilities and a calibrated confidence. Hard checks, constraints, and rules still decide; a failed hard check forces a specific outcome, never a guess. Three decider backends are supported: an LLM client solvi.models.llm for any OpenAI-compatible chat-completions server (OpenAI, OpenRouter, vLLM, llama.cpp, Ollama); a decision service solvi.models.systemone for anything speaking the System One API; or a local ONNX checkpoint solvi.models.decider("solvi-base") that runs offline on CPU. solvi-base is a 150M ModernBERT-base cross-encoder distilled from solvi-large, roughly 50 ms per question on CPU (ONNX fp16, 4 threads). Model cards on huggingface.co/solvi-ai report what each model was measured on, its performance, and its limits.

Catalog: functions, checks, extraction, and rules

The catalog registers facts and rules. @cat.fn marks a computation; argument names are the facts it reads, the function name is the fact it sets, and type hints become the fact types validated by pydantic at runtime. @cat.check marks a boolean guard; with hard=True and a then mapping, a failure forces a specific answer. @cat.extract pulls a value from text and returns it as a quote with character offsets. @cat.rule binds an answer rule to a question name. Questions carry a requires list; without a rule, a small answer head trains from labelled examples or a readable learned rule list.

Strategist plans minimal execution; parallel steps run as inputs arrive

For each question, the strategist walks backwards from the rule's arguments through catalog signatures to the initial state keys, adding the question's required parts and every check that touches a computed fact. Unneeded catalog entries are skipped; the flow records why each part was taken or skipped. Each part runs once even if multiple questions need it. Hard checks and their inputs run first; if one fails, only steps for settled questions are skipped. With System(..., workers=8), independent steps run in parallel threads as soon as their inputs are ready. Results enter the computed state with provenance; extracted values keep their quote. Each step record is hashed and chained to the previous one in flow order, so the trace does not depend on scheduling.

Answer primitives, evidence quotes, and replay verification

Every answer carries a value, confidence, and reason. The type system declares primitives: Maybe[T] where "not stated" (solvi.Unknown) is a real answer distinct from abstention; Span[float] for an exact text span parsed; Rank[...] for the top k in order; Estimate[0, 7, 14] for a number with an interval; and evidence quotes on any answer (Claim with evidence, Question with require_evidence=True), each checked in the text from rules or a model. The trace is fully replayable; re-execution confirms the answer or identifies the altered step. solvi.show.show prints answers, the planned flow, computed state, and replay result in one call.

solvi.build learns fast paths; solvi.Agent reuses knowledge across runs

solvi.build fits System 1, calibrates its error promise on unseen examples, stores every decision, and reports what it chose. On four benchmarks (Banking77, Abt-Buy, CUAD, RAGTruth) the defaults matched or beat hand-written setups with every promise kept, in a quarter to half of the code. solvi.Knowledge persists facts with sources (never the system's own answers), goals, gates, and an action model learned from outcomes; retraction removes everything derived from a retracted item. solvi.Agent acts on this knowledge: System 1 where the knowledge predicts the action works, System 2 search where it does not. Gates and predicted refusals are hard checks — protection by default; justified risk (RiskBudget) is an option that lowers protection cost without promising parity. In a toy crafting world, the first run took 61 steps all decided by System 2; the same world again took 12 steps, 11 by System 1.

Guard validates tool calls before execution

The agent proposes tool calls as name and arguments. solvi.Guard checks that the tool is in the catalog, arguments validate against their types, values quoted in the conversation are not replaced by a tool output saying "ignore previous instructions," and policies (limits, roles, allow-lists) pass as hard checks. An optional decider can ask "did the user ask for this?" under act_guard and perturb. The call is then allowed, denied with reasons, or escalated to a person. Every decision is a stored, replayable trace; any framework's tool calls go through guard.check or guard.call. On τ-bench retail, no call it let through was refused by the environment.

Browser execution and model zoo

All solvi components run in the browser via Pyodide: no server, no GPU, nothing typed leaves the page. Each space installs the pinned solvi release (1.0.0). Available models include solvi-ai/solvi-large (typed decisions, 396M, preview), solvi-ai/solvi-base (same answers on CPU/ONNX, 150M, preview), solvi-ai/extract-base (fields by description), and solvi-ai/extract-receipts. Ready systems assemble from these; a custom part can be plugged in at every extension point — store, slow path, strategist, head, decider, action model, and more.