Skip to main content

Caching and resume

Pass A caches each section’s extraction on disk at graphify-out/.intent_cache/ (next to graph.json), keyed by a content hash of the exact prompt (source file + section path + body), the resolved Pass A model, and a fingerprint of the system prompt + schema. Each entry is written the moment its section completes.
  • Automatic reuse. Re-running the same corpus serves already-extracted sections from cache; only new or edited sections (their body hash changed) re-extract. The pre-flight estimate, ETA, cost gate, --dry-run projection, and the post-Pass-A checkpoint all size Pass A off the cache-miss sections only, so a fully cache-warm re-run projects ~0 calls/tokens for Pass A.
  • Resume after an abort. If a run aborts (the measured mid-run spend guard — any pass — or the post-Pass-A --max-tokens whole-run checkpoint before Pass B), every section that finished before the abort is already cached. Just re-run (after raising --max-tokens/--max-cost-usd, or narrowing --docs) and Pass A resumes from where it left off; the abort message says so.
  • --no-cache bypasses the cache completely for the run: no reads, no writes.
  • Force a refresh by deleting graphify-out/.intent_cache/; there’s no separate “clear cache” flag.
  • Use a consistent --docs spelling. The cache key embeds the source-file path string as --docs produces it, so re-running against the same docs with a differently-spelled --docs (e.g. relative vs. absolute) can miss the cache even though the files are the same.

What a run costs

On an API backend you pay for real tokens. whyfile scales with your docs, not your lines of code (the base graphify graph for source is AST-based and free), so the bill tracks how much prose you point it at. Pass A does the reasoning on sonnet at roughly 2 cents per doc section; Pass B (anchoring, on haiku) is the token heavyweight — its candidate batches measured ~7.6k tokens per call — but haiku pricing keeps its dollar cost near Pass A’s. On the subscription backend (the fallback when no API key is set) there is no per-token bill: the run goes through your Claude Pro/Max plan, so there is no separate dollar cost. But the run is not free: it still consumes the tokens in the table below against your plan’s usage limits, the same tokens an API run would be billed for. “No bill” is not “no cost”. Passes A,B,C (the default); adding Pass D moves the total by well under a dime. The first row is provider-measured on this repository — the post-#54/#55 verification run, with Pass A on sonnet and B/C on haiku; its pre-flight estimate was 583k tokens / 2.10,sotheestimatelandedwithin152.10, so the estimate landed within 15%. (The earlier all-`sonnet` run over the same corpus billed 4.53 — issue #55 — and is preserved as tests/fixtures/measured_run_2026_07_17.json, the estimator’s calibration ground truth.) The other two rows are the tool’s own projections at representative sizes, priced at the published API rates (sonnet 3/3/15, haiku 0.80/0.80/4.00 per Mtok). An earlier version of this table under-reported by ~6x: it was built from pre-measurement heuristics, which issue #54 recalibrated against provider truth. These are cold-cache, from-scratch figures: because Pass A caches every section, a re-run costs about $0 on API (only new or edited sections re-extract); on subscription a re-run still costs about zero tokens, for the same cache-hit reason.

Per-unit economics

The measured 2026-07-17 provider run above works out to roughly 0.005perreconstructedintentnode;alargermeasuredexternalcorpusrunlandedat 0.005 per reconstructed intent node**; a larger measured external-corpus run landed at **~0.44 per source doc (denser docs, more nodes each — the per-node figure is the stabler unit). The 2026-07-17 run’s Pass A was 68% output tokens — cost scales with what the corpus yields (how many intent nodes and edges it reconstructs into), not just its size on disk. A terse, decision-dense doc can cost more than a long, narrative one; input-token intuition (estimating from doc word count alone) undershoots. --dry-run and the end-of-run manifest report this honestly: est_cost_usd is a real number on the API backend, but null on subscription (never 0.0, which would read as free), paired with a billing note stating the estimated token count and that it counts against your plan. To see the exact projection for your own corpus before spending anything, --dry-run prints it as JSON and makes no extraction calls:

Where the estimate comes from — and how it learns

Every projected figure carries a basis label so a guess can never read as a measurement:
  • heuristic:* — the shipped constants, recalibrated against a full provider-measured run of this repository (they reproject that run’s 587.5k measured tokens within ~1%).
  • sidecar — a Pass-B-only run anchors the intent nodes a previous run produced, so the estimate is that exact node count.
  • cache / cache+heuristic:7x_sections — when Pass A is in the run, Pass B anchors Pass A’s output: nodes replayed from the extraction cache count exactly, and cache-miss sections are estimated by a 7-nodes-per-section prior. On a first backfill nothing is measured yet, so the prior carries the whole projection — the label says so, and a note flags that projections after Pass A switch to the real extracted count.
  • calibrated:last-5-runs — the estimator’s best mode: once intent_runs.jsonl (written next to your graph on every run) holds provider-measured history for a pass, the estimate becomes your own corpus’s median measured tokens-per-call times the projected call count. Your second run is estimated from your first.
At the end of every run, stderr prints the reconciliation so you can see how the estimate did: Tokens: est ~583k → measured ~508k (measured-provider).

The spend guards

Three layers keep a run inside the budget you approved, and they differ in what they consume:
  1. Pre-flight prompt (estimate): before anything is spent, the whole-run projection is checked against --max-tokens and (API backends) --max-cost-usd; if it’s over, you’re asked — or the run refuses, non-interactively — before the first call. --yes skips only this prompt.
  2. Measured mid-run guard (never an estimate): during the run, the provider’s real token counts are checked between call completions in every pass, and dollars are priced at each pass’s own model rates. If actual spend crosses a ceiling — even though the estimate said it wouldn’t — in-flight calls are cancelled and the run aborts with the manifest marked aborted. Completed Pass A sections stay cached, so the money already spent is not wasted on a re-run.
  3. Post-Pass-A checkpoint (estimate): once the real intent-node count is known, the remaining-work projection is re-checked before Pass B commits the bulk of the spend.
--max-tokens 0 and --max-cost-usd 0 disable their respective ceilings. The middle layer exists because of a measured failure: the first real API run cost 6.8x its estimate while an armed --max-cost-usd never fired — every guard at the time consumed estimates.

Ongoing cost per PR

Updating the intent layer on a PR is cheap: cents on an API backend (a ~$0.03 floor), or a small token draw against your plan’s usage limits on the subscription backend, no separate bill either way. A per-PR update re-anchors existing intent plus any changed doc sections; the Pass A cache means unchanged sections cost nothing on either backend. See What a run costs for the full breakdown.

Two CI postures

Example workflows for both live in examples/workflows/.

Protecting what you paid for

Reconstruction is the only tier that costs real money. It lives in graphify-out/, which is gitignored — so a filesystem accident there is unrecoverable unless you have a copy. (This is not hypothetical: a field deployment lost a completed backfill of ~10.5k intent nodes to a stray symlink, with no backup.) Archive the paid tier before anything risky — a big refactor, a directory reshuffle, a machine migration:
That writes the intent sidecar, the enriched view, the Pass A cache, the raw response archive, and the run manifests. It is read-only on graphify-out/ — it cannot damage the artifacts it is protecting. graph.json is deliberately excluded: graphify rebuilds it for free from your code. Restore into a repo that lost them:
Import refuses to overwrite existing artifacts unless you pass --force, so it cannot silently replace a live intent layer. If a command reports a symlink loop or a broken link under graphify-out/, inspect before deleting anything (ls -l graphify-out). Removing what looks like a broken directory is exactly how the field incident above destroyed real artifacts — remove or re-point the link, never its target, until you have confirmed which is which.