Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

canact

Probe an LLM against this host's tools and return a capability card the host can use: how many tools to send, which edit format to pick, whether to enable XML fallback, and whether to wrap JSON in a repair layer.

Catalog flags (supports_function_calling: true, context: 128k) are priors. canact spends seconds of real prompts on this model, this template, and this tool schema, then writes host policy.

Default features are empty. A library pin uses default-features = false plus runtime. The CLI and MCP server need --features cli.

Read the card defines host, host policy, the fixed probe tool set, the context ladder, and the envelope.

Getting started

Install

cargo install canact --locked --features cli

Prebuilt archives ship on GitHub Releases (macOS, Linux, Windows x64):

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/canact/canact/releases/latest/download/canact-installer.sh | sh
brew trust canact/tap
brew install canact/tap/canact

Windows (Scoop, x64):

scoop bucket add canact https://github.com/canact/scoop-bucket
scoop install canact/canact

Library (runtime only, no CLI):

canact = { version = "0.10", default-features = false, features = ["runtime"] }

MSRV is Rust 1.95.

First probe

canact probe --provider ollama --model llama3.2:3b --cheap --json

The built-in Ollama URL is http://127.0.0.1:11434/v1. That string is the crate constant OLLAMA_BASE_URL. Nothing reads an environment variable of that name. Pass --base-url to use a different URL.

--cheap is --suite=policy (host-policy fields, 4k ladder). --full adds sequencing and the 8k/16k ladder. --suite=all adds diagnostics.

Cloud hosts need a key before any HTTP call. The first match in this table wins. --api-key is visible in shell history and the process list; prefer an env var. canact probe --no-login skips stored Grok and Claude Code logins. Env vars still apply. canact probe --dry-run prints the provider, redacted base URL, suite, and probe names, then exits. It does not read a login, the cache, or the network. canact probe --fail-on weak exits 2 when a finished row is Weak. --fail-on degraded also exits 2 for Medium. A skipped row or a probe error does not count. An unknown value exits 1.

Provider labelDefault URL when --base-url is omittedWhat is tried, first match winsStored login
emptyhttps://api.openai.com/v1, unless a key selects the xAI, Anthropic, or OpenRouter host--api-key, else OPENAI_API_KEY, else XAI_API_KEY then GROK_API_KEY then Grok login, else ANTHROPIC_AUTH_TOKEN then ANTHROPIC_API_KEY then Claude Code login, else OPENROUTER_API_KEYGrok login unless --api-key, OPENAI_API_KEY, OPENROUTER_API_KEY, XAI_API_KEY, or GROK_API_KEY is set. An Anthropic env key does not skip it. Claude Code login runs only when nothing earlier matched. --no-login skips only those two helpers.
xai, grok, api.x.ai, x.aihttps://api.x.ai/v1--api-key, else XAI_API_KEY, else GROK_API_KEY, else Grok login. Ignores OPENAI_API_KEY.~/.grok/auth.json. A named xAI provider still loads it when OPENAI_API_KEY is set.
grok-build, xai-grok-build, cli-chat-proxy.grok.com, and the -messages pairhttps://cli-chat-proxy.grok.com/v1Same xAI packSame Grok login
claude, anthropic, api.anthropic.comhttps://api.anthropic.com/v1--api-key, else ANTHROPIC_AUTH_TOKEN, else ANTHROPIC_API_KEY, else Claude Code. Ignores OpenAI and xAI.Claude Code login
openrouter, openrouter.aihttps://openrouter.ai/api/v1--api-key, else OPENAI_API_KEY, else OPENROUTER_API_KEY. No stored login.none
groq, api.groq.comhttps://api.groq.com/openai/v1--api-key, else GROQ_API_KEY. No stored login.none
amazon-bedrock, bedrockhttps://bedrock-runtime.us-east-1.amazonaws.com--api-key, else AWS_BEARER_TOKEN_BEDROCK. No stored login.none
openai-codex, codexhttps://api.openai.com/v1--api-key, else OPENAI_API_KEY. No stored login.none
ollama, localhost, 127.0.0.1, ::1, 0.0.0.0http://127.0.0.1:11434/v1 (this constant, not an environment variable)nonenone
lmstudiohttp://127.0.0.1:1234/v1nonenone
vllmhttp://127.0.0.1:8000/v1nonenone

Auth, a missing model, and connect failures abort the suite.

--json prints the host-policy envelope. Field definitions and the rest of that object are in Read the card. Dry runs that do not call a model live in the repo examples/ directory.

Read the card

The first command is:

canact probe --provider ollama --model llama3.2:3b --cheap --json

The built-in Ollama URL is http://127.0.0.1:11434/v1. That string is the crate constant OLLAMA_BASE_URL. Nothing reads an environment variable of that name. Pass --base-url to use a different URL.

Definitions

A host is the program that will call the model. That is the CLI user, Aider, Cline, or a ProbeClient. The card is for that host.

Host policy is the set of envelope fields a host branches on: how many tools to send, which edit format to pick, whether to fall back to XML, and whether to repair JSON. It is not a rank of the model.

The fixed probe tool set is what canact sends. tool_selection sends eight names: read_file, edit_file, doc_set, search, run_command, list_dir, md_replace_section, and write_file. parallel_tool_scale sends read_file only. That set is not the caller's schema. This page does not describe a host-supplied tool list.

The context ladder climbs 4096, then 8192, then 16384. --cheap and --suite=policy stop after 4096. --full and --suite=all continue through 8192 and 16384.

canact probe --json prints the host-policy envelope. The cache file is probes.json. Entries last 30 days. The cache key includes the suite version (currently 97). fromCache is true only when this print came from that file. cacheable means the result may be stored. A context-window rejection keeps the last passing ladder rung, and the card stays cacheable. A provider safety stop is not a score, so the card is not stored. A malformed tool call is a completed Weak. Do not paste the envelope over probes.json.

Envelope fields

The Getting started page and the README host-policy table list the fields a host branches on. The envelope also always includes these:

FieldMeaning
modelModel id that was probed
providerProvider label
overallLowest measured level among tool calling, JSON output, and instruction following (weak, medium, or strong). weak when none of those three were measured.
canUseToolsNative or XML tool calling completed Medium or stronger
supportsVisionThe vision probe completed Medium or stronger
toolSelectionStatuscompleted, skipped, unprobed, or error for tool_selection. Read this before treating maxTools 10 as measured Weak.
effectiveContextTokensMeasured usable context, in tokens, or JSON null until a suite writes it
probedContextFloorHighest passing ladder rung when the climb is incomplete, or JSON null
skipExpensiveTrue on the policy suite
suitepolicy, full, or all
advertisedContextTokensCatalog or flag prior, or JSON null
probedAtUnix seconds when the card was stored
scoreScalemin 0.0, max 1.0, strongMin 0.8, mediumMin 0.4
probesPolicy dimensions. multiTurnTaskSequencing is present on policy with status skipped.
diagnosticsEmpty on policy and full. Populated on --suite=all.

maxOutputTokens is inserted only when it was measured. It is omitted otherwise. constraintPlacement is inserted only on --suite=all, and only when a placement was measured. Both fields are already in the README host-policy table.

On a policy run, agentLoop is JSON null and probes.multiTurnTaskSequencing.status is skipped. A full suite that completes sequencing sets agentLoop to full, assisted, or single.

Exit code 2

When the model cannot use tools, canact prints the envelope on stdout first, then this line on stderr, then exits 2:

error: cannot use tools (native and XML both failed to complete)

JSON on stdout is still valid. A shell with set -e stops on that exit code.

Library

ProbeRunner stays on feature runtime. ProbeRunner::new is the full suite and paid concurrency (64). ProbeRunner::new_throttled is the policy suite and free concurrency (3). The README example uses new_throttled. .suite() changes the suite only. .throttled() changes concurrency only. .cheap() and .full() set both.

A snippet that constructs OpenAiCompatClient needs features runtime and openai. The key below is None because this URL is local Ollama. Do not put a raw key in the source.

canact = { version = "0.10", default-features = false, features = ["runtime", "openai"] }
#![allow(unused)]
fn main() {
use canact::{CatalogPriors, OpenAiCompatClient};

let _client = OpenAiCompatClient::new(
    "http://127.0.0.1:11434/v1",
    None,
    "llama3.2:3b",
    "ollama",
    CatalogPriors::default(),
)
.expect("client");
}

Matrix

canact matrix prints pretty JSON. Each cell is pass, degraded, fail, or skipped. There is no --json flag and no human table. --provider is optional. Omit it to include every cached provider. The command does not call a model.

MCP

canact mcp --api-key-env XAI_API_KEY --base-url http://127.0.0.1:11434/v1

--api-key-env is the name of an environment variable. The tool cannot name a different variable, and it must not receive a raw key. --base-url is the probe URL. The tool cannot replace it. --allow-base-url lets the tool pass a base URL that is not a loopback host. --allow-cache lets the tool pass a cache path outside the default cache directory.

Other commands

The credential table is in Getting started. canact probe --no-login skips stored Grok and Claude Code logins. Environment variables and --api-key still apply. See #288.

canact cache path prints the cache file. canact cache list prints the stored rows. This page does not copy that output. See #287.

canact export --all writes the Aider pair and cline.modelinfo.json and leaves stdout empty. The file list stays in the README. See #289.

canact probe --dry-run prints the provider, redacted base URL, suite, and probe names, then exits. It does not read a login, the cache, or the network. See #290.

canact probe --fail-on weak exits 2 when a finished row is Weak. --fail-on degraded also exits 2 for Medium. A skipped row or a probe error does not count. An unknown value exits 1.

Architecture

canact is one crate: a library plus an optional canact binary.

src/lib.rs          public types and re-exports
  cache             probes.json (30-day TTL, not the CLI envelope)
  endpoint          provider URL and host-family hints
  types             CapabilityProfile, levels, host-policy fields
  error             Auth / NotFound abort; Transient stays session-local
                    a window rejection keeps the last ladder rung
                    a safety stop is Transient and is not cached
                    a malformed tool call is a completed Weak
  runtime           ProbeClient, ProbeRunner, graders
  adapters/openai   OpenAI-compat, Anthropic, Ollama, xAI
src/bin/canact.rs   CLI, export, and `canact mcp` (feature `cli`)
  export            Aider / Cline overlays

Default features are empty. A library pin uses default-features = false plus runtime. The binary needs --features cli.

The host-policy card is the product: max_tools, edit-format ladder, XML fallback, and JSON repair. Graders live next to the probes they score. Types and CLI goldens are the source of truth.

Tests: cargo test --locked --features runtime --lib. Local gate: make check.

Contributing

See CONTRIBUTING.md in the repository.

Local gate: make check. Commits need a DCO sign-off (git commit -s). PR titles use conventional types (feat, fix, perf, docs, chore, ci).

Security

See SECURITY.md for how to report a vulnerability.

Private reports go through GitHub Security Advisories. Release archives include Cosign .sigstore.json and SLSA .intoto.jsonl assets. The Release workflow also uploads a CycloneDX SBOM, canact-sbom.cdx.json. Git release tags are GPG-signed annotated tags (git verify-tag vX.Y.Z).