v0.47.0
AFT v0.47.0
128 commits since v0.46.0 — 250 files, +48k/−8k, the largest release in the project's history. Three arcs converge: the daemon transport moves to wire v2, project roots survive restarts warm, and a long list of CPU, memory, and latency pathologies found by running AFT as a fleet-wide daemon are fixed at the mechanism level.
Subc wire v2
The daemon transport is cut over to subc protocol 0.9 (wire v2): typed route channels carry an epoch, and both endpoints validate direction-agnostically, so stale channel state can never route a reply into a newer binding. Around the cutover, the recovery paths hardened end to end:
- Reconnects, stale route handles (
stale_route_handle,unknown_channel), and transient initial-attach failures retry with bounded backoff instead of failing the tool call. - A same-root bind with a higher epoch implicitly replaces the older route when the daemon's best-effort Goodbye did not arrive.
- The connection file's wire version is validated before TCP attach: a v1/v2 mismatch fails loud instead of producing framing timeouts.
- Tool-call phase traces (queue → translate → execute → format → egress) make slow daemon calls diagnosable from logs.
Operator note: upgrade daemon and module together; the wire versions do not mix (by design, and now by explicit error).
Restarts keep roots warm
Previously, every host restart tore down each project root's warm state: on rebind the module re-verified whole corpora by content hash and force-rebuilt callgraph stores — with many roots restarting at once, this was the "fans spin up after every restart" storm.
Now a transient unbind is cheap by design. Route teardown stops all background work for the root (configure tails, queued maintenance, in-flight index builds — via a new executor-level job cancellation with cooperative checkpoints) but keeps the watcher running and the artifacts resident. Watcher events accumulate while unbound and replay after rebind, so nothing is missed and nothing is rebuilt. The heavy teardown (watcher stop, strict re-verification, forced callgraph reconcile) now happens only when a root stays unbound past a 30-minute idle TTL, or when its directory is deleted.
Measured in fleet use: rebind after a module bounce went from seconds of hashing plus a full callgraph rebuild per root to microseconds, with AFT tool latency staying flat across 10+ consecutive daemon deploys.
Underneath this sits a publication-safety layer for background index builds: receiver generations and epochs gate which completed build may install or persist, callgraph pointer publication is fenced at the actual generation swap, forced-rebuild demand uses monotonic tokens, and freshness memos are ticket-fenced against watcher events that arrive mid-verification. A stale worker from a previous configure can no longer overwrite a newer one's state, in RAM or on disk.
Performance
- Indexed grep/glob 2-4x faster. Both paid a full ignore-aware filesystem walk per query just to report scope emptiness; scope is now derived from the index snapshot (grep p50 1.6-4.8ms, glob 3.3-3.9ms).
- Cross-project (borrowed) search ~40x faster. Every borrowed query re-hashed the external corpus (Blake3 over thousands of files) for a freshness census; the parsed borrow index is now cached by artifact identity (medians ~470ms → 1-12ms).
- Semantic re-embeds coalesce under a 15-second quiet window instead of firing per edit; changed files stay masked from semantic results until re-indexed. Interactive query embeddings get a dedicated budget (
semantic.query_timeout_ms, default 3000ms, clamped 500-15000) with lexical fallback, so a busy embedding backend can't stall search. - Binds acknowledge before heavy work. Index deserialization moved off the bind-ack path to post-ack maintenance; warm rebinds skip redundant re-verification via a stat-first memo (rebind cost 574ms → 19µs on a warm root).
- Idle roots release memory. Root-scoped artifacts are attributed, evicted after the idle TTL, and reloaded on demand; on macOS the allocator is asked to release pages after sweeps. Fleet idle CPU went from ~180% to under 4%, RSS from multi-GB to a few hundred MB.
- Re-export resolution can no longer explode. Cyclic wildcard re-exports (barrel files) made missing-symbol resolution explore
branching^depthpaths — one bad resolution pinned a worker at 100% CPU indefinitely. The resolver now memoizes with depth dominance, making it O(files) while provably preserving every resolution the capped walk finds. - Watcher drains are sliced and budgeted; callgraph store writes run on a dedicated worker off the interactive lanes; incremental refresh no longer reloads the module-resolution index per file (156s → 0.3s for a single-file refresh); duplicate lexical ranking in external hybrid-search fallbacks eliminated.
LSP
- Scoped
aft_inspectdiagnostics no longer leak: the per-file loop drains server events as it goes, and every document opened for the inspection is closed on exit (RAII), fixing unbounded LSP memory growth on pull-capable servers (#160). - Freshly auto-installed language servers are picked up without a restart (#153).
- Windows: an unversioned System32
onnxruntime.dllis no longer mistaken for a compatible runtime, anddoctor --fixdownloads the managed one (#157).
Bash
bashwithwait: truedetaches to a background task the moment a new user message arrives, instead of blocking the conversation until the command ends — on both the standalone and daemon paths.- Session-owned bash artifacts (stdout/stderr/exit files) are readable under
restrict_to_project_root(#159); mutations remain strictly root-bound. - Detached background tasks survive daemon module restarts; the module detaches its registry and leaves children running on SIGTERM.
- Under the daemon, bash children inherit a login-shell-enriched PATH (with fish support and standard tool directories appended), so
bun,cargo, and friends resolve without shell profiles.
Tool correctness
deleteis honest: deleting a symlink to a directory is no longer a silent no-op, and an all-failed batch returnssuccess: falsewith per-path reasons.apply_patchfailures render the nearest-miss candidate (the actual file lines at the best-scoring location), turning a fail-read-retry loop into a single-shot fix.- Batch
edits[]items supportreplaceAll; out-of-rangeoccurrenceerrors teach the 0-indexing at the point of failure. - Relative
aft_safetycheckpoint paths resolve against the project root, not the daemon's working directory. - Pi tool schemas advertise bounded integers as integers (spec-compliant, better tool-call generation on strict providers).
- Image reads no longer time out from queueing behind other image work under load.
Storage
Callgraph and inspect caches collapse from harness-partitioned to root-keyed storage: one writer per artifact (fenced file leases with heartbeats), any number of read-only sessions (marker files with generation GC), and a copy-based migration that preserves the existing store — a 19 GB callgraph universe migrates without a rebuild, with a legacy read fallback if migration cannot complete. Plugin-less invocations (daemon module, aft warmup, bare CLI) now default to the shared CortexKit data root, ending split-brain caches. Borrowed semantic artifacts from a different embedding backend are rejected rather than served wrong.
Languages
Groovy, Gradle build scripts, and Jenkinsfiles get outline-tier support: aft_outline, aft_zoom, semantic indexing, and AST search (27 supported languages).
Observability
Durable logs land under <storage_root>/logs/ with 20 MB × 5 rotation and dead-process cleanup after 7 days. Health probes answer from a dedicated control lane with try-lock-only reads, so a busy module is never misreported as dead. Status reports carry per-root memory snapshots including what an idle eviction actually released.
Verification
Beyond the standard gates (full serial Rust suite, release-profile storm tests, TS suites across both plugins, Windows/macOS/Linux CI), this release's lifecycle and publication-safety work went through seven adversarial review rounds and a multi-model panel audit, each round closing verified counterexamples down to zero open findings — plus a full day of fleet dogfooding on the release candidates, which is where the warm-restart and CPU-spin fixes were validated live.