Skip to main content

intact: Self-Healing File Edits for Agents

Pipeline Status Test Coverage License Python Version PyPI Wheel PyPI Types

pre-commit version pyrefly ruff

A reconciliation ladder that heals stale or drifted edits instead of failing them, with atomic multi-file transactions, patch ingestion, undo, durable anchors, and an LSP client — as a library, a CLI, and an MCP server.

[!NOTE] Genesis. intact was designed, built, reviewed, and shipped end-to-end by Claude (Fable 5) as an autonomous long-run engineering experiment — from mining two weeks of real agent-session ledgers for the failure class worth killing, through a fleet of builder/critic subagents and adversarial review rounds, to the benchmark, the extraction, and this release. The human operator set the charter, held the release gates, and found the name. The eval numbers below are measured on that same fleet's real failures — this package was built by the population it serves.


The problem, measured

An independent audit of two weeks of this fleet's own Claude Code session ledgers found that the single largest recurring cost line wasn't a wrong answer or a slow model — it was harness friction: an agent's builtin Edit tool failing an exact-string match against text that had drifted underneath it (formatter passes, a stale read, whitespace normalization), 99 instances totaling roughly 190K tokens over the window, each one paid as a full read-diff-retry round trip. tact exists to kill that class of failure at the source: it heals what's mechanical (whitespace, Unicode punctuation, indentation shift, small textual drift) through a symbolic, self-reporting reconciliation ladder, and it refuses loudly — never silently — the moment a match is genuinely ambiguous or absent, because a wrong guess in a file edit is worse than an honest failure. It ships as a library, a CLI, and an MCP server, so the same reconciliation core backs whichever front end a given agent harness actually calls.

The error cloud

Two oblique views of a 3-D PCA error cloud. Dark gray spheres are other preventable agent errors, amber spheres are edit failures tact could likely prevent, and cyan spheres are tact usages that healed drift.

This is a privacy-normalized PCA projection of 30 days of lifecycle summaries, the full ledger-derived edit-failure benchmark, and tact's real reconciliation counters. Sphere radius grows linearly with a generous capped estimate of tokens wasted—or saved by a heal. The populations worth finding in the dark cloud are amber (an observed error this build uniquely heals) and cyan (an actual non-exact tact reconciliation).

The committed points contain no sessions, repositories, paths, prompts, file contents, users, or timestamps. See the methodology and reproducible generator.

Install

pip install intact
# or
uv add intact

The CLI and library work with no extras. The MCP server needs the mcp extra:

uv add "intact[mcp]"

60-second quickstart

As a library

from tact import read_file, apply_edits, Edit

r = read_file('config.py')
apply_edits(
    'config.py',
    [Edit(old='DEBUG = True', new='DEBUG = False')],
    expected_sha256=r.sha256,  # advisory -- a mismatch heals, it never blocks
)

As a CLI

# a builtin exact-match Edit just failed -- heal it in one round trip
uv run tact heal config.py --old "DEBUG = True" --new "DEBUG = False"

# or drive the whole read -> edit cycle through tact directly
uv run tact --json edit config.py --old "DEBUG = True" --new "DEBUG = False"

As an MCP server

Register tact (adapt the project path for your own install):

uv run --extra mcp --project /path/to/tact tact-mcp

Then call tact_edit/tact_heal/tact_definition/… as MCP tools instead of the harness's builtin Read/Edit. See docs/howto.md for per-harness registration snippets (Claude Code, Droid, Codex, Cursor).

As the fleet's native edit route

# one command serves Claude Code, Codex, Factory Droid, or Gemini's JSON hook contract
tact hook --harness codex --mode deny

# verify the install, state directory, MCP extra, telemetry, and harness compatibility
tact doctor

shadow observes, warn adds native context where the harness supports it, and deny refuses eligible builtin edits with a concrete tact next action. New files, notebooks, unrecognized tools, and malformed payloads fail open. The ordinary test suite proves the contracts symbolically; the billable live_harness suite drives each real agent CLI and is skipped unless explicitly enabled. See Fleet-native routing.

The reconciliation ladder

find_block descends five rungs of mounting tolerance, stopping at the finest rung that yields a unique match:

Rung What it forgives Example
EXACT Nothing — verbatim line equality old_string matches byte-for-byte
CANON Unicode punctuation substitution a curly "quote" pasted in for a straight one
RSTRIP Trailing whitespace drift a formatter stripped trailing spaces on save
INDENT A uniform leading-whitespace shift the block moved one indent level deeper
FUZZY Small textual drift, bounded by per-line similarity a stale line number after a nearby edit landed

Two or more candidates at any rung is a structured ambiguity refusal (every candidate shown, resolved only by a --near hint); zero candidates at every rung is a structured no-match refusal. Rungs 1-4 are true equivalence relations; rung 5 is a similarity ball, not transitive — which is exactly why ambiguity-handling concentrates there. See docs/explanation.md for the full framing.

The numbers

Replayed against the fleet's own real edit-failure history (tact.eval, ledger-mined, no near hint supplied — any multi-candidate rung is a refusal, never a guess):

Corpus Cases Healed False heals Correct refusals
Public subset (this repo, tests/) 59 43 0 1
Full private fleet corpus (eval-of-record) 111 68 0 1

The public subset is a privacy-reviewed 59-fixture slice of the full 121-fixture harvest (62 fixtures held back for operator-stack detail or private prose, not for reconciliation performance — see tests/fixtures/ledger_eval/README.md for the curation). Cases that don't heal are mostly no_match, not misses: a large share are modified_since_read failures where the stored file snapshot legitimately predates the real edit, so a refusal there is reality reflected correctly, not a ladder weakness.

Features

  • check/edit — resolve-then-atomically-write a batch of edits, with a dry-run twin (check_edits) that previews without touching the file.
  • apply_many transactions — resolve every file in a batch before writing any of them; any failure blocks the whole transaction, nothing partial is ever written.
  • heal — fix a failed builtin Edit(path, old_string, new_string) in one read-only round trip.
  • undo — a content-addressed pre-image journal behind every write, with a redoable undo verb.
  • Durable anchors — capture a block's position so it survives a session/compaction boundary; resolve_anchor re-finds it, biased by its own remembered index.
  • delta/read_since — a cheap "what changed since this hash" re-read, backed by the undo journal's blob store.
  • rename_symbol — LSP-computed cross-file renames, converted into one apply_many transaction (the one verb that is not LSP-optional).
  • apply_patch — ingest a unified diff or a fenced old/new block pair straight into an Edit batch.
  • LSP navigationdef/refs/hover/sym/diag, name-first, backed by a persistent, resilient pyrefly/ruff client.
  • Native fleet routing — one content-blind policy rendered into Claude Code, Codex, Factory Droid, and Gemini hook contracts, with reversible shadow/warn/deny modes.
  • Fleet diagnostics + telemetrytact doctor reports readiness; privacy-minimal counters and events retain outcomes without paths, content, sessions, repositories, or users.

Honest limits

tact matches line-by-line, so a paragraph reflow (a prose formatter that rewraps text across different line boundaries) is not a whitespace/indent variant it heals — expect a no-match refusal and a manual re-read there. Ambiguity refusals need a --near hint or a more distinctive block; tact will not pick a near-tie for you. rename_symbol is LSP-backed only — it never falls back to a textual find-replace, since that risks renaming the wrong binding, so it structurally refuses when no live LSP is available. The undo journal keeps the newest 32 pre-images per file and is index-pruned, not globally garbage-collected — it is acceptable-loss state, not a full version history.

Provenance

The reconciliation ladder's canonicalization table and fuzzy-matching primitives in tact/reconcile.py are a port, with attribution, of parts of dirac (Apache-2.0, version 0.4.11) — see NOTICE and reconcile.py's own module docstring for exactly what was ported and how it was adapted.

License

Apache-2.0. See LICENSE and NOTICE.


Versioning

This project follows workflow-gated semver: the minor version records the highest-completed py-workflow step (0.1.0-0.8.0), with 0.9.0 (beta) gated on >=9 external users and 1.0.0 on a maintainer social guarantee. The current version 0.8.1 remains in the report-complete 0.8 band; this patch release adds fleet routing and observability without claiming the nine-external-user beta gate. See the normative rubric and the overview.

Release files for intact 0.8.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for intact 0.8.1
File Size Uploaded
intact-0.8.1.tar.gz 90.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for intact 0.8.1
File Interpreter ABI Platform
intact-0.8.1-py3-none-any.whl Python 3 none any Details

Total release size: 195.0 kB

Release files / intact-0.8.1.tar.gz

Download URL intact-0.8.1.tar.gz
Size 90.8 kB
Tags Source
SHA-256 checksum
How to use checksums
e0b81bf4c8c3bd3d5c4d1b463c46132dd459cbab176f381c2f53995919ae11ca
BLAKE2b-256 checksum
How to use checksums
c1311dac8b257a177510a2cc7163b8bbe4e33729e5d6890802f8fa744e23821e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / intact-0.8.1-py3-none-any.whl

Download URL intact-0.8.1-py3-none-any.whl
Size 104.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
13dcfb40043d0e8b5ab8b4e270c86102ed5cf1c6bfc7492d8c4427dc1096f6b8
BLAKE2b-256 checksum
How to use checksums
636cd46f8096b29f82878d5d0d6cc73a154e176bbe8deb0b189b7263e4c21939
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

1.0.0

2 release files

This release

0.8.1 This release

2 release files

0.8.0

2 release files

0.7.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page