Skip to main content
Documentation menu

Features

Verified mode

chanto --verify (or [guardrails] enabled = true) stops the agent from declaring victory until the evidence agrees:

  • Done-gate: when the model gives its final answer after changing the project, Chanto runs the project's checks. They're auto-detected (cargo test, go vet && go test, npm test, pytest through the first of python3 and python that has it, else unittest, make test) or set with checks = [...]. By default it runs only the tests related to the change (see below). Failures go back to the model, up to max_attempts (default 3). An answer that changed nothing is checked once, with the full suite, so "done" without doing the work doesn't slip through.
  • Test protection: existing test files are snapshotted when the turn starts. Deleting them, removing or changing assertions, changing an existing test's body, or adding skip markers (@skip, it.skip, #[ignore], t.Skip, …) is reported as soon as it happens and then fails the gate with "fix the code, not the tests". New tests are fine. Outside --verify the same check applies without a gate: the agent is told to restore the test and fix the code, and a finish that still stands on the change is sent back. If the requested behaviour genuinely conflicts with an existing test, the agent keeps the code, leaves the test and explains the conflict.
  • Finishing on a failed run: outside --verify, a final answer after the agent's own last check failed is sent back with the failure output (twice at most), and a failure that was only re-run at a narrower scope (a full suite that failed, then one file that passed) is sent back as unverified unless a later passing run covers the same tests.
  • No redundant runs: if the agent's own last run of the same checks passed with no edits since, that run is the evidence and isn't repeated. Only a plain run counts: not piped (| tail hides failures), not filtered to some tests (-k, a test name, --collect-only), and covering at least what the gate would run. A run of more files or packages than the related selection counts (pytest tests/ covers pytest tests/test_a.py); a run of fewer doesn't.
  • Baseline: failures are reported as new or already failing; if only already-failing ones remain after one push, the turn ends with ⚠ Not verified naming them instead of retrying. When the gate runs the full suite, the baseline is recorded before any change, alongside the first model call. When it runs related tests, the baseline is taken only if they fail: the same selection runs once more with the files changed this turn temporarily put back as they were (then restored), so passing turns pay nothing for it. The changed versions are saved under ~/.chanto/history/putback/ while that run is going; if Chanto is killed meanwhile (even with kill -9 or by closing the terminal), a small watcher process puts them back right away; failing that, the next start in that project restores them and says so.
  • Evidence: the final answer ends with what passed and how much of the suite that was, e.g. _Verified: pytest -q -x tests/test_x.py tests/test_y.py (3s) passed, related to your changes; full suite not run._, or _Verified: cargo test --quiet(40s) passed (full suite:Cargo.toml changed)._, or an explicit ⚠ Not verified with the failing output. Checks that find no tests to run verify nothing, so that's ⚠ Not verified too, with a pointer to checks = [...].
  • Exit code: with -p, a run that ends ⚠ Not verified exits 3 (0 = verified or nothing to verify, 1 = error or interrupted), so scripts and CI can tell them apart.
  • Tests that don't finish: a check that hits its time limit (timeout_secs under [guardrails], default 600) ends the turn ⚠ Not verified, without a retry, a full-suite fallback or a baseline run, since each would hang the same way. If the agent's own run of the same tests just timed out, with no edits since, the gate doesn't run them again.
  • Your instructions come first: if a check fails in code you told the agent to leave alone, it leaves it and says so; the run then ends ⚠ Not verified.

Related tests. From the files changed this turn (by edits, shell commands or subagents), each detected test command is narrowed:

RunnerRelated selection
pytestchanged test files; test files named after a changed module (test_<mod>.py, <mod>_test.py, under a matching package directory when nested); test files that import it (import pkg.mod, from pkg.mod import …, from pkg import mod), up to 50 files: pytest -q -x <files>
unittest (discover)the same files as modules: python3 -m unittest -q tests.test_mod …
cargoin a workspace, the crates holding changed files plus the workspace crates that depend on them: cargo test -p a -p b; a single crate runs its whole suite
gothe changed packages with their subpackages, plus packages that import them: go vet ./pkg/x/... ./pkg/y && go test ./pkg/x/... ./pkg/y
jest / vitest (from npm test's script)npx jest --findRelatedTests <files> / npx vitest related --run <files>

A new file nothing imports (a reproduction script) doesn't widen the selection. Other checks (make test, npm run typecheck, your own commands) always run in full.

When it runs the full suite instead: shared test or build setup changed (conftest.py, pytest.ini, pyproject.toml, setup.py/setup.cfg, tox.ini, requirements*.txt, Cargo.toml/Cargo.lock, build.rs, go.mod/go.sum, package.json and lockfiles, jest/vitest/tsconfig config, Makefile, CI files); more than 20 files changed; a changed file has no related tests or isn't the runner's language; the changed files can't be determined; or the related run itself can't run (a path or package it names isn't found, no tests collected). It also just runs everything when that's cheap: a project with 20 or fewer test files, or a full suite this session already measured at 30s or less. The evidence line says which, and why.

Forcing the full suite: chanto --verify-full, scope = "full" in config, or /verify full for the rest of the session (/verify related switches back; /verify shows the current setting).

toml
[guardrails]
enabled = true
checks = ["cargo clippy -- -D warnings", "cargo test"]   # default: detected
scope = "related"     # or "full"
max_attempts = 3
protect_tests = true
oracle = false        # true: the oracle reviews the diff once before the checks run

Project-level [guardrails] runs commands, so it only applies in trusted folders.

Checks (configured or detected), goal and outcome criteria, team checks and formatters run the way the agent's Bash commands do: in the sandbox when it's on, with the same writable places (the project, temporary directories, tool caches), and without secret-named environment variables. A check that needs a token gets it through [secrets] pass_env. Under a network allowlist or network = "deny", a check that needs the network fails as a Bash command would. Language servers start without secret-named variables too. Detecting pytest never imports code from the project.

The oracle. oracle is a built-in subagent type, next to Explore and general-purpose: a read-only reviewer (Read, Grep, Glob, read-only Bash, web) prompted to find bugs, wrong assumptions and broken callers in a plan or diff, and to answer LGTM when there's nothing to fix. It runs on oracle_model (top-level setting, e.g. a stronger hosted model), else the main model. The model can consult it any time through the Task tool (subagent_type: "oracle"). With oracle = true under [guardrails] and verified mode on, when the model first gives its final answer after changing files, the oracle reviews that turn's diff; its findings go back to the model once (it fixes what's real or says why not), and then the done-gate runs as usual. An LGTM, a failed review or an empty diff goes straight to the gate. A custom agent named oracle replaces the built-in.

Edit this page on GitHub Report a problem

Commands, settings, errors: type a few words.