Commit Graph

5 Commits

Author SHA1 Message Date
kshitijk4poor b36f654271 test(profiles): pin projects/tree single-flight as a behaviour contract
Replace the attribute-presence check (asserts a cache_clear attribute the decorator does not
expose) with one invariant: eight concurrent projects/tree requests run the profile scan once
and each caller gets its own copy of the payload. The scope-suite fixture now disables the TTL
instead of calling a non-existent clear hook, which silently no-op'd.
2026-09-09 21:11:56 +05:30
moken627-hub 35af06c009 fix(desktop): coalesce projects/tree sidebar scans and memoize raw config parses
The all-profiles sidebar polls GET /api/profiles/projects/tree, the one
heavy sidebar endpoint that was not wrapped in @_sidebar_singleflight_cache.
Every poll fanned out list_profiles() + _build_project_tree() over every
profile (51 on this box, ~17k SKILL.md files walked), and each profile
resolution re-parsed its config.yaml because read_user_config_raw() ran
uncached. On a 2-vCore VPS running 'hermes serve' for the Desktop remote
backend this pinned both cores (py-spy: 110-116% sustained, HostHighCPU).

- Wrap get_profiles_projects_tree in the existing single-flight cache,
  matching get_profiles_sessions_sidebar (5s TTL, errors[] not cached).
- Memoize read_user_config_raw() on a (st_dev, st_ino, st_size,
  st_mtime_ns) fingerprint under _CONFIG_LOCK, same strategy as
  read_raw_config(). Hits return a deepcopy so write-back round-trips keep
  their fresh-dict semantics; parse errors are never cached; the docstring
  no longer claims 'no caching'.
- Tests: memo semantics (deepcopy isolation, inode-replace reparse, error
  non-caching, parse-count), a wrapper-presence pin for both heavy sidebar
  endpoints, and a cold-cache autouse fixture in the scope tests (the 5s
  TTL otherwise leaks one test's payload into the next).

Measured on the affected box: list_profiles 1.9s -> 0.16s warm, serve CPU
112% -> 22-34%, host CPU 50-70% -> ~26%.
2026-09-09 21:11:56 +05:30
Brooklyn Nicholson 3d81650c2f fix(desktop): a failed sidebar scan keeps the rows it could not re-read
The sidebar reports a profile it could not scan as HTTP 200 with an empty
page and errors=[{profile}]. The renderer merges that page keeping only
working, pinned, and selected rows, so every idle Yesterday / This-week
session disappears until a later scan succeeds — and the 5s coalescing cache
then serves the same empty payload back for the rest of its TTL.

Carry the previous rows forward for exactly the profiles named in errors[],
keyed by profile::id so a twin id in another profile is never stitched in.
Profiles that scanned cleanly are still authoritative, so a genuinely empty
page with no errors still clears the list. Per-profile usage and truncation
flags follow the same rule rather than zeroing under a list that was kept.

The legacy per-slice fallback stamps errors on the slice that actually
failed, so a cron read failure can no longer blank recents.

Part of #73847
Part of #88528

Co-authored-by: AKAZIK-py <AKAZIK-py@users.noreply.github.com>
2026-09-01 22:42:19 -05:00
Teknium 67a1c1ed1a fix(dashboard): use a fixed sidebar cache TTL (no HERMES_* env var for non-secret config) 2026-08-15 00:32:53 -07:00
Lucas Oliveira f0cfe5a56f perf(dashboard): bound multi-profile sidebar polling 2026-08-15 00:32:53 -07:00