intact: Self-Healing File Edits for Agents
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.
intactwas 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
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_manytransactions — 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 builtinEdit(path, old_string, new_string)in one read-only round trip.undo— a content-addressed pre-image journal behind every write, with a redoableundoverb.- Durable anchors — capture a block's position so it survives a session/compaction boundary;
resolve_anchorre-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 oneapply_manytransaction (the one verb that is not LSP-optional).apply_patch— ingest a unified diff or a fenced old/new block pair straight into anEditbatch.- LSP navigation —
def/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/denymodes. - Fleet diagnostics + telemetry —
tact doctorreports 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 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| intact-1.0.0.tar.gz | 115.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| intact-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 246.1 kB
Release files / intact-1.0.0.tar.gz
| Download URL | intact-1.0.0.tar.gz |
|---|---|
| Size | 115.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3c6fa3a651fb7309e85fbabff0314de91a759d2e6a40d82a71bf7a762d5b2c68
|
|
BLAKE2b-256 checksum How to use checksums |
141a2527306ddf6dad7cec1fc981328ccd34f53c34b31a6afb4c44c276514139
|
| 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-1.0.0-py3-none-any.whl
| Download URL | intact-1.0.0-py3-none-any.whl |
|---|---|
| Size | 130.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
476b3fde83c61ccb2fa5686fa55c2c3ad4d9eb5e0d1e153c4e5d9bde829a3bad
|
|
BLAKE2b-256 checksum How to use checksums |
0772aa990d46dd477222ac7c5452f5b364ccaaa47c9eb7caa931bfdc2825c857
|
| 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}
|