Skip to main content

Tycho — a free, offline, open-source local verifier that proves an AI agent actually did what it claims, from git, the filesystem, process exit codes, and the harness event stream. No account, no LLM in the trust path.

Project description

Swail

Tycho

by Swail

The system of record for what your coding agents did.

CI PyPI Python License Deps


"never accept the model's summary as evidence, only artifacts it can't fake by narrating"

Tycho runs as a completion hook on your agent's harness. It records what every turn actually did — files touched, commands run and what they returned, what the agent claimed — and checks that against the artifacts: git, the filesystem, exit codes, the harness event stream.

Only a hook on the event stream sees the process. A diff sees a result. CI sees a result. A second model sees a diff. Tycho was there when the agent ran pytest, got exit 1, and then said tests pass.

Instant, free, silent, always-on — no API key, no second session, no LLM. Nothing leaves the machine (no LLM anywhere in the trust path). Stdlib only, Python ≥ 3.11. Claude Code and Codex. Linux, macOS, Windows — WSL2 counts as Linux, install inside the distro.

On Claude Code, Tycho costs you no tokens and no context: it has a channel that reaches you and not the model. On Codex it costs a little of both — see Per-harness notes.

Install

curl -fsSL https://swail.dev/tycho/install.sh | sh   # standalone binary, no Python
brew install swail-labs/tap/tycho                    # same binary, upgradable
uv tool install tycho-cli                            # have Python? (pipx works too)
npx @swail-labs/tycho <command>                      # npm wrapper, same binary

The PyPI package is tycho-cli (tycho was taken); it installs the tycho command. Pick one channel per machine — npm -g and brew both want to own bin/tycho.

The installer sets Tycho up as it finishes, so there is nothing to run afterwards. If you installed another way, or said no:

tycho install           # once, for every repo on this machine

That writes two things — the completion hook, status badge and slash commands into your harness's user-level config, and one line (.tycho/) in your global git ignore file. It names every path before it writes any of them.

The second write is what lets Tycho stay out of your repos: with .tycho/ ignored machine-wide, it never edits any repo's .gitignore, so git status stays clean everywhere. It only runs inside git repos, and defers to any repo that has its own install.

That's it. Every repo you open is verified from the next turn on, and Tycho says so once, the first time it meets a repo.

tycho off               # stop verifying this repo   (tycho on to resume)
tycho uninstall         # remove it from this machine
TYCHO_AUTO=0            # only verify repos where you ran `tycho init`

Per-harness notes

Claude Code — nothing to do.

Codex (the CLI and the ChatGPT desktop app alike) — two things to know.

Approve Tycho's hooks the first time Codex asks. Codex will not run a hook it hasn't shown you: the first session after one appears says "hooks need review before they can run", and until you approve, the config is read and nothing runs. tycho doctor reports it if you skipped it. After tycho install that approval is once for the machine; after tycho init it's once per repo.

Tycho uses a little of your context and usage here. Claude Code gives a hook two separate channels — one the human reads, one the model reads — so a verdict reaches you for free. Codex has one field, and both of you read it. So whenever Tycho has something to say on Codex, it ends the agent's turn and hands it the text to show you, which spends a small amount of context and one model turn. That is the cost of being told at all: the alternative, and what Tycho did before 0.2.1, is silence.

It stays small and it stays bounded. Verdicts are capped to a handful of lines with tycho show for the rest; a routine turn says nothing; the relay is opt-in and limited to three re-checks per turn; and anything sent this way carries an instruction telling the agent to relay it and stop, not to act on it. Nothing about the verification itself changes — the checks, the record and the verdict are the same offline, stdlib-only code on every harness.

Adopting a repo

tycho install deliberately writes nothing into your repos. When you want a repo to carry Tycho with it — shared with your team, checked in CI:

tycho init              # in the repo

That adds a .tycho.toml to commit (scope, relay — settings the whole team gets) and a prepare-commit-msg hook stamping a Tycho-Attestation: trailer onto commits an agent worked on. It merges with hooks you already have, backs up what it changes, and refuses to touch a config it can't parse. On a machine where you never ran tycho install, tycho init also wires that repo's hooks, so a single repo works on its own.

Upgrading from 0.1.x

Your per-repo installs keep working untouched, and take precedence over a machine-wide one. Two things changed names: tycho uninstall now removes Tycho from the whole machine — it used to mean this repo, so it refuses and asks when you run it in a repo that has its own install. Use tycho off to stop verifying one repo, or tycho uninstall --here to remove its hooks. tycho init --global still works as a spelling of tycho install.

After a turn

Most turns print nothing. Tycho speaks when a check says the turn isn't proven, when the prose claims done but the evidence doesn't reach it, when a run of proven turns breaks, or when a turn touches far more files than this repo's turns usually do. A condition that fired the last two turns goes quiet, so a standing failure doesn't repeat at you forever.

🔍 Tycho: FAILED — file_state: docs/product-direction.md — missing
   ✓ attempted  · executed  ✓ artifact_changed  ✓ claim_supported
   2 files · pytest -q… failed · +7 more
   `tycho show` for the full receipt

The second line is the acceptance ladder: attempted → executed → artifact_changed → claim_supported. Unreached rungs render ·, not — a gap in the evidence isn't a failure. Verdicts are VERIFIED · FAILED · STALE (evidence exists but predates the code) · INDETERMINATE (ran, couldn't conclude) · UNSUPPORTED · OVERRIDDEN.

What you can do with it

tycho show                       # the full receipt for the last turn (or `show <id>`)
tycho log                        # what agents did here, newest first (--verdict, --since, -n)
tycho blame src/app.py           # which turns touched this file, what they claimed, what backed it
tycho review                     # which changed hunks no test covered and no command exercised
tycho backfill                   # record what agents did here before Tycho was installed
tycho verify                     # re-run the engine over a session on demand
tycho count --ledger             # catch and blind rates, per model and per check
tycho exec -- pytest -q          # run it and put the real exit status on the record
tycho attest --verify            # check a commit's trailer against the record
tycho scope add 'src/**'         # bound where the agent may edit (opt-in; `--exclude` denies)
tycho relay --on                 # let the agent see its own verdict and keep working (off by default)
tycho rewrite --on               # route a piped runner through `tycho exec` for you (off by default)
tycho off                        # stop verifying this repo (`tycho on` resumes)
tycho doctor                     # is the hook installed, current, and firing?

tycho help lists everything, including hook, install, init, on/off, uninstall, statusline, run, override and update.

Exit codes — one per adverse kind, so a gate picks what blocks. 1 FAILED, 3 STALE (verify); 6 unexercised (review --exit-code); 7 trailer mismatch (attest --verify); 5 installed-but-broken (doctor); 2 usage, 4 couldn't complete. The hook itself always exits 0 and never blocks your agent.

What Tycho won't claim

Tycho answers can't tell rather than guessing, so there are places it deliberately says less than you might expect:

  • blame PATH:LINE is file-level. The record stores which turns touched a file, never which lines. Joining through git blame would put a confident wrong name on a line.
  • review speaks about the record, not execution. "No command Tycho recorded ran after this hunk" — never "this line never ran". There's no per-line coverage here.
  • The attestation proves consistency, not authenticity. Unkeyed SHA-256 over a record this machine wrote: tamper-evidence against accidents, not a defence against a motivated agent. --verify can answer cannot tell, and a pruned record must never read as a forged one.
  • tool_call_provenance is advisory. Two claim families (web, issue trackers), matched on family presence only. Tuned to never emit a false FAIL, at the cost of recall.
  • A backfilled turn has no verdict, and never will. tycho backfill replays transcripts written before Tycho was here, but most checks are functions of state that no longer exists — whether an edited file is on disk now, an mtime now against a run three weeks ago. So those rows record what the transcript proves (files, commands, claims, when) and read UNVERIFIED. They're excluded from every rate in count --ledger. Tycho will not tell you what it "would have caught".
  • verifier_integrity raises the cost of tampering; it doesn't make it impossible. It reports a turn that edits .tycho/, .tycho.toml, a harness hook config or a git hook — via the edit tools, a shell redirect, or a mutating command. It cannot see a write smuggled through an interpreter (python -c "open('.tycho.toml','w')") or a path the shell expanded from a variable, and a process running as you can always reach the file. Tycho's own state is judged inside this repo only — another tree's .tycho/ is scope_drift's business, not a verdict here. It is the check [checks].disable may not switch off.
  • A piped runner's exit status is gone before Tycho can read it. pytest | tail -20 hands the harness tail's status, so the verdict falls back to reading pytest's summary line — inference, and only as good as the vocabulary in engine/runlog.py. tycho exec -- pytest | tail -20 fixes it at the source, and tycho rewrite --on does that for you. Rewriting is opt-in because Claude Code matches your Bash(...) rules against the rewritten command, so an allow rule you already granted can stop covering it; under --permission-mode bypassPermissions there are no rules to void and it always applies.
  • Nothing polls in the background. A hook that died goes undiagnosed until someone runs doctor.

Configuration

Settings in .tycho.toml (scope, relay, override, rewrite), state in <repo>/.tycho/. Command strings, evidence and agent prose are pattern-filtered for secrets before hitting disk — a match becomes a visible [REDACTED] — but it's best-effort, so keep .tycho/ out of git regardless.

TYCHO_HOME · TYCHO_CLAUDE_HOME · TYCHO_AUTO · TYCHO_STATUS · TYCHO_RELAY_MAX · TYCHO_TURNS_MAX · TYCHO_COMMANDS_MAX · TYCHO_NO_UPDATE_CHECK · TYCHO_INSTALL.

The one thing that touches the network: a SessionStart hook asks pypi.org once a day whether a newer Tycho exists, disclosing your version and IP. TYCHO_NO_UPDATE_CHECK=1 turns it off.

License

Apache-2.0. Contributing: CONTRIBUTING.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tycho_cli-0.2.1.tar.gz (830.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tycho_cli-0.2.1-py3-none-any.whl (235.4 kB view details)

Uploaded Python 3

File details

Details for the file tycho_cli-0.2.1.tar.gz.

File metadata

  • Download URL: tycho_cli-0.2.1.tar.gz
  • Upload date:
  • Size: 830.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tycho_cli-0.2.1.tar.gz
Algorithm Hash digest
SHA256 e134bc2d711c553957eafe45bd666c2fd318e55f7801b32fdbe9921f3b0070c2
MD5 3e2eb0eeb2bc3054133a3d289a1c5d0e
BLAKE2b-256 f93f52c08b7ee894b52d22c7a6223ab79e67e0bccebc1bc00f5e565fc5955b21

See more details on using hashes here.

Provenance

The following attestation bundles were made for tycho_cli-0.2.1.tar.gz:

Publisher: release.yml on swail-labs/tycho

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tycho_cli-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: tycho_cli-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 235.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tycho_cli-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e94dce2c93ffdcbfc74e835c5569178c866222862b8dc27b2962b37e458df480
MD5 f1a56df9e04b98ad22e80cc8dbb001e0
BLAKE2b-256 da6fdf0bd889421d76650bf40f51efb080e9f2f5908ae218c889943652631d18

See more details on using hashes here.

Provenance

The following attestation bundles were made for tycho_cli-0.2.1-py3-none-any.whl:

Publisher: release.yml on swail-labs/tycho

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page