Result so far
Repeat discovery latency is fixed for every mounted project. The three projects that previously never cached at all — nova, imas-codex and imas-ambix — now answer a repeat request in 6 to 15 milliseconds instead of paying their full cold cost on every call. The plan's repeat target of 100 ms is met with two orders of magnitude to spare. The cold-scan target is not met and is discussed under Cold scan remains slow.
Discovery cache repair
Commit 5922bcf, merged as cb16b49. Two files:
reckon/serve.py (+251/−61) and a new
tests/test_discovery_cache.py (+219).
Measured latency, same server, before and after
Wall-clock through GET /_discover/<project>. "Before" is the first call
after a clean restart followed by two repeats; "after" is the same sequence against the
rebuilt server.
| Project | Before, repeat | After, repeat | Change |
|---|---|---|---|
| nova | 5.39 s | 0.012 s | 449× |
| imas-codex | 4.15 s | 0.006 s | 692× |
| imas-ambix | 2.36 s | 0.012 s | 197× |
| imas-efit | 0.023 s | 0.014 s | already cached |
| imas-standard-names | 0.004 s | 0.003 s | already cached |
What changed in the code
_has_external_dependencies is deleted. It was a boolean veto that disabled the
cache for the entire project whenever any plan declared a cross-project dependency. In its
place, _external_dependency_projects collects the specific mounted projects
actually referenced, and their own discovery signatures are folded into the cache entry
alongside the local one. An unchanged external input is now a cache hit; a changed one
invalidates only what depends on it.
_git_first_committed keeps its output shape but splits the HEAD lookup from
history population. A per-repository {head, times} record is retained in memory;
an unchanged HEAD performs only rev-parse and reuses the map, while an advanced
HEAD queries the old..new range alone and merges the additions with
setdefault, so an existing first-commit timestamp is never overwritten by a
later one. The bare except Exception: return {} is replaced by a
_run_git helper that logs timeout, non-zero exit and start failure distinctly
and still returns the ctime fallback.
Gate evidence
Five new tests in tests/test_discovery_cache.py, all passing in 0.63 s. They
prove: repeat discovery for two mounted fixture projects — including the cross-project
dependant — runs exactly 0 git subprocesses and 0 parse_meta
calls; mutating a referenced provider flips the dependant's
effective_status from blocked to active; an unchanged HEAD issues only
rev-parse; an advanced HEAD issues exactly one range query; and a timeout plus
an exit-code-7 failure each log a distinct warning while returning the file's
ctime.
Full suite after merge: 1,944 passed, 4 failed in 65 s. The four failures
reproduce identically on a clean main with no changes applied, so they are
pre-existing and unrelated. They are being repaired under
Pre-existing failures.
Cold scan remains slow — an open negative result
The plan's second latency criterion, a cold scan of the largest repository under 3 s, is
not met: imas-efit still takes 14.8 s and nova 7.1 s on the first call after
a server restart. This is a direct consequence of the locked
git-creation-source decision, which chose an in-memory HEAD-keyed cache and
explicitly declined disk persistence on the grounds that restarts are rare next to the
per-request cost being removed. That reasoning holds for steady-state use and the measured
result confirms it, but it leaves every server restart re-paying a full history walk.
The criterion and the decision were authored in the same session and contradict each other; the criterion is the one that was wrong. Recorded here rather than quietly amended, because the honest options are to persist the creation map to disk — the option the decision deferred — or to restate the criterion as a steady-state target. That is the lead's call.
Reconnaissance
Run r-20260821T135033054402-prior-art-scout, read-only, 442 s, gate passed.
Censused four repositories and classified 17 candidate module groups and 36 named tests.
Its binding verdicts shaped the implementation above: extend the existing cache and git
helper in serve.py rather than adding a module; reject
_has_external_dependencies as the hit predicate outright; reuse
capabilities.py's explicit-version and atomic-replace invariants as pattern
only, never as an import.
It confirmed the test gap that let the defect survive: no existing test counted git
subprocesses or parse calls across a repeated discover_plans. It also
returned two findings outside its brief — the ReadyGate rejection gap, and
SHA-256 evidence that the imas-ambix and imas-efit SPA asset copies are byte-identical to
each other but differ from canonical reckon in 4 of 4 sampled files, making reckon the only
safe edit point and reckon sync the only safe propagation path.
SPA legibility audit
Run r-20260821T135054536357-spa-legibility-audit, commit 38ac267,
merged as 1d38e38, gate passed. Published at
docs/research/spa-legibility-audit.html: 15 ranked findings against live served
payloads — 2 critical, 6 high, 5 medium, 2 low; by class, 6 not-shown, 4 shown-but-unexplained,
5 stale-or-wrong.
Measured inputs: a live GET /_discover/reckon of 171,172 bytes carrying 67
artifacts and 9 sprint resources, with S5 and S9 both marked active while
focus stayed on S5; one plan whose authored status disagrees with its effective status
because of 3 open gates; 12 resource-version tokens the shell discards; and a live
GET /crew returning 9 runs spanning three projects through a table with no
project column.
The two critical findings are both about the surface lying by omission. Rank 1: the loader
completes the whole discovery fetch before rendering, and ReadyGate shows only
"Loading plan state…" with no elapsed time and no rejection branch at all, so a
failed discovery renders as an indefinite load — the same appearance as an unreachable
server, and the likely explanation for this plan's originating report. Rank 2: the status
menu flashes "updated" without awaiting the canonical write, and that write reports
ok: true even when the server is unreachable and only local storage changed.
SPA legibility fixes landed
Six of the fifteen audit findings are fixed, including both criticals. Every node ran in an isolated worktree on codex; each commit touched only its declared paths.
| Rank | What was wrong | Commit |
|---|---|---|
| 1 · critical | The ready gate had no rejection branch, so a failed discovery rendered as an indefinite load — indistinguishable from an unreachable server. Now reports elapsed seconds while pending and an endpoint-specific alert on rejection. | 85082f3 |
| 2 · critical | A status write flashed success without awaiting persistence, and that write returned ok even when the server was unreachable and only local storage changed. Writes now await the promise and distinguish canonical save with version, local-only fallback, and conflict that reverts the optimistic value. | dbad7d7 |
| 4 · high | Sprint cards were draggable and the drop target changed their visible status, but the override lived only in component state and a refresh discarded it. Affordance removed: 2 state hooks, 3 drag handlers, 3 card attributes and the drop label, pending a canonical sprint mutation. | 4e0ee94 |
| 5 · high | The board read a justification field that does not exist and never read done_when, so all nine sprint resources lost their item rationale and completion contract on screen. | 4e2fc2b |
| 7 · high | The crew table rendered nine runs spanning three projects with no project identity, under a project-branded shell. | fc79eac |
| 8 · high | The fleet page twice directed the reader to a hard-coded legacy home path the server may not read. The resolved mounts file is now published in the projects payload and rendered from that value; zero legacy occurrences remain. | 8bdf35b |
Verified against the live server after restart: mounts_path resolves to
~/.config/reckon/mounts.json; repeat discovery measured at 13 ms (nova),
37 ms (imas-efit) and 4 ms (reckon). Suite 1,951 passed / 0 failed.
Carried, not landed: ranks 3, 6, and 9 through 15. Rank 3 is the live integrity case — S5 and S9 are both marked active while the legacy index names S5 as focus, and the shell silently picks one. Its SPA remedy is carried; whether S5 should close is a sprint decision left to the lead. Rank 6 needs authored-versus-effective status rendered on compact surfaces. Ranks 9 to 15 are medium and low.
Pre-existing failures found in passing
The four suite failures are unrelated to this plan but were repaired under it rather than walked past. Both root causes were established by reproduction on a clean tree.
-
test_agent_context.py::test_home_target_without_repository_has_only_user_contextfails becauseagent_context.pytreats any directory containing a.gitentry as a repository root using an existence check alone. An empty/run/user/39486/.gitdirectory, created 2026-08-19 and containing zero entries, is enough to satisfy it, so a temporary home created beneath that path reports a repository root where there is no repository. -
Three
test_skill_contracts.pyassertions fail because the recent skill commits deliberately retired slot-pool vocabulary in favour of roster authority, while the assertions still require the removed strings. The size assertion is a genuine budget breach rather than stale vocabulary: the document is now 12,558 estimated tokens against a 12,000 bound, and the bound is being respected rather than raised.