Skip to content

[LUM-498] Fix inferCategory main-thread hang by caching results and offloading computation#24274

Merged
tkheyfets merged 2 commits into
mainfrom
devin/LUM-498-1775658155
Apr 8, 2026
Merged

[LUM-498] Fix inferCategory main-thread hang by caching results and offloading computation#24274
tkheyfets merged 2 commits into
mainfrom
devin/LUM-498-1775658155

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Apr 8, 2026

Fixes a main-thread stall in the Skills panel caused by inferCategory running redundantly on every SkillsStore.objectWillChange notification — including non-skill property changes like isLoading and loadedBodies. Two targeted changes eliminate the redundant work:

  1. SkillsManager.rebuildCategoryMap now caches inferred categories keyed by skill ID with a fingerprint (name + "\0" + description) for invalidation. When the fingerprint matches, the cached category is reused (O(1) dictionary lookup); when metadata changes, inferCategory is re-run for that skill. This reduces the hot path from O(N×K) string matching to O(N) dictionary lookups in the common case (skills unchanged), while still correctly re-classifying skills whose name or description changes.

  2. IdentityPanel.fetchSkills moves the inferCategory loop into a Task.detached so it runs off the main actor, preventing the keyword-matching work from blocking UI during initial skill load.

Both are safe because inferCategory is a non-isolated pure function over Sendable value types with no shared mutable state. The categoryFingerprints dictionary is marked @ObservationIgnored so it doesn't trigger unnecessary SwiftUI view updates.


Review & Testing Checklist for Human

  • Build in Xcode — CI skips macOS builds entirely, so Swift compilation has not been verified. Confirm both SkillsManager.swift and IdentityPanel.swift compile without errors or warnings.
  • Verify Skills panel loads correctly — Open the Skills panel, confirm skills appear with correct category assignments in sidebar counts and constellation view.
  • Install/uninstall a skill — Verify the category map updates correctly for newly added skills (cache miss → inferCategory called) and removed skills drop out of the map.
  • Verify Identity panel constellation — Open the Identity panel, confirm the constellation graph renders skills in the correct category groupings.

Notes

  • Task.detached was chosen over Task { } because the enclosing fetchSkills() is called from IdentityPanel (a SwiftUI View), so a plain Task inherits main actor isolation. Task.detached ensures keyword matching runs on the cooperative thread pool.
  • The fingerprint uses a null byte (\0) separator to prevent false cache hits from coincidental name + description collisions.
  • Future improvement: skip rebuildCategoryMap entirely when skill IDs haven't changed, but the fingerprint-based caching is simpler and sufficient.

Link to Devin session: https://app.devin.ai/sessions/ee5299fa3a7142c693319834eb37d19b
Requested by: @tkheyfets


Open with Devin

… (LUM-498)

SkillsManager.rebuildCategoryMap now reuses cached values from the
existing categoryMap, only calling inferCategory for skills not already
in the cache. This eliminates redundant string allocation and keyword
matching on every objectWillChange notification.

IdentityPanel.fetchSkills now runs the inferCategory loop in a
Task.detached so it executes off the main actor, preventing the main
thread hang during initial skill load.

Co-Authored-By: tkheyfets <timur@vellum.ai>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 560decc714

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

map.reserveCapacity(skills.count)
for skill in skills {
map[skill.id] = inferCategory(skill)
map[skill.id] = categoryMap[skill.id] ?? inferCategory(skill)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recompute category when skill metadata changes

Using categoryMap[skill.id] as the sole cache key means inferCategory is never rerun for skills whose id stays constant but whose name/description changes. SkillsStore.fetchSkills(force: true) can refresh skill metadata without changing IDs, so category filters and the constellation grouping can become stale for the rest of the session after catalog updates or local skill edits. Invalidate per-skill cache entries when classification inputs change (or key the cache by a metadata fingerprint) so refreshed skill data is reflected.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 115258c. rebuildCategoryMap now tracks a fingerprint (name + "\0" + description) alongside each cached entry via a parallel categoryFingerprints dictionary. The cached category is only reused when the fingerprint matches; any metadata change triggers re-inference. Stale entries are naturally evicted because both maps are rebuilt fresh each call (skills removed from the list drop out).

The @ObservationIgnored annotation on categoryFingerprints ensures SwiftUI doesn't trigger view updates for fingerprint changes — only the public categoryMap drives observation.

devin-ai-integration[bot]

This comment was marked as resolved.

Track a fingerprint (name + description) alongside each cached category
entry so that rebuildCategoryMap re-runs inferCategory when a skill's
classification inputs change while keeping the same ID.

Co-Authored-By: tkheyfets <timur@vellum.ai>
@tkheyfets
Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@tkheyfets tkheyfets merged commit ed26bf8 into main Apr 8, 2026
6 checks passed
@tkheyfets tkheyfets deleted the devin/LUM-498-1775658155 branch April 8, 2026 15:01
noanflaherty pushed a commit that referenced this pull request Apr 8, 2026
…ffloading computation (#24274)

* fix: cache inferCategory results and move computation off main thread (LUM-498)

SkillsManager.rebuildCategoryMap now reuses cached values from the
existing categoryMap, only calling inferCategory for skills not already
in the cache. This eliminates redundant string allocation and keyword
matching on every objectWillChange notification.

IdentityPanel.fetchSkills now runs the inferCategory loop in a
Task.detached so it executes off the main actor, preventing the main
thread hang during initial skill load.

Co-Authored-By: tkheyfets <timur@vellum.ai>

* fix: invalidate category cache when skill metadata changes

Track a fingerprint (name + description) alongside each cached category
entry so that rebuildCategoryMap re-runs inferCategory when a skill's
classification inputs change while keeping the same ID.

Co-Authored-By: tkheyfets <timur@vellum.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: tkheyfets <timur@vellum.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant