Skip to content

feat: lazy skill loading with per-session deduplication - #5938

Closed
RufusLin wants to merge 1 commit into
NousResearch:mainfrom
RufusLin:feat/lazy-skill-loading
Closed

feat: lazy skill loading with per-session deduplication#5938
RufusLin wants to merge 1 commit into
NousResearch:mainfrom
RufusLin:feat/lazy-skill-loading

Conversation

@RufusLin

@RufusLin RufusLin commented Apr 7, 2026

Copy link
Copy Markdown

feat: Lazy skill loading (with per-session deduplication)

The Problem

Every time skill_view() is called for the same skill within a session, the full SKILL.md body is injected into the conversation history as a tool result. On long sessions, this means the same skill content can appear multiple times in the context, consuming tokens unnecessarily. Context bloat!

My Proposed Solution

This PR adds per-session deduplication to skill_view(). After the first successful load of a skill's full content, subsequent calls within the same task session return to the model a lightweight message instead of the full skill file:

{
  "success": true,
  "already_loaded": true,
  "name": "my-skill",
  "message": "Skill 'my-skill' was already loaded earlier in this session. Refer to its instructions in your context. If you cannot find them (e.g. after context compression), call skill_view again with force=True to reload."
}

Feel free to change that message if you like!

The model can call skill_view("my-skill", force=True) to bypass the cache and receive the full body again — for example, after context compression has removed the earlier load from the conversation window.

Changes

tools/skills_tool.py

  • Added module-level _loaded_skills: Dict[str, Set[str]] dict (keyed by task_id) and a threading.Lock for thread safety
  • Added force: bool = False parameter to skill_view()
  • Added deduplication check: if the skill was already loaded in this session and force=False, return the lightweight stub
  • After a successful full load, record the skill name in _loaded_skills[task_id]
  • Added reset_loaded_skills(task_id=None) function to clear the cache (called after context compression)
  • Updated SKILL_VIEW_SCHEMA with the force property
  • Updated registry.register handler to pass force through

run_agent.py

  • Call reset_loaded_skills(task_id) after context compression, alongside the existing reset_file_dedup(task_id) call — ensures the model can reload skills whose content was summarised away

agent/skill_commands.py

  • Pass force=True to skill_view() in _load_skill_payload() — slash-command skill invocations always need the full body. Sorry, had to add a parameter here, no other way.

cron/scheduler.py

  • Pass force=True to skill_view() in the cron job skill loader — scheduled tasks always need the full body

Tests

Added TestSkillViewDeduplication in tests/tools/test_skills_tool.py with 6 cases:

  • First load returns full content
  • Second load returns the stub
  • force=True bypasses the stub and returns full content
  • reset_loaded_skills() clears the cache so the next call returns full content
  • Deduplication is scoped per task_id — independent sessions don't interfere
  • Linked file loads (file_path set) are never deduplicated

Behavior

  • Deduplication applies only to main SKILL.md loads. Linked file loads (file_path argument) are always passed through.
  • The force parameter is also exposed in the tool schema so the model can use it directly in its tool call JSON. This is important if the context has been compressed and it can't find the skill we "claim" is loaded.
  • Thread safety: _loaded_skills is protected by a threading.Lock for environments that run concurrent tool calls.

Thank you for Hermes Agent, I use it 24/7! - Rufus Lin

Add per-session deduplication to skill_view() so that repeated calls
within the same task session return a lightweight acknowledgement
instead of re-injecting the full SKILL.md body into the conversation
context.

- tools/skills_tool.py: _loaded_skills state, force param, dedup
  logic, reset_loaded_skills(), schema update
- run_agent.py: call reset_loaded_skills() after context compression
- agent/skill_commands.py: pass force=True for slash-command loads
- cron/scheduler.py: pass force=True for cron job loads
- tests/tools/test_skills_tool.py: 6 new TestSkillViewDeduplication cases
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have tool/skills Skills system (list, view, manage) labels Apr 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related to #12379 and #13980 (lazy skill loading via config option). This PR takes a different approach: per-session deduplication of skill_view() calls rather than lazy-loading from system prompt.

1 similar comment
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related to #12379 and #13980 (lazy skill loading via config option). This PR takes a different approach: per-session deduplication of skill_view() calls rather than lazy-loading from system prompt.

@RufusLin

Copy link
Copy Markdown
Author

Closing as stale — branch is far behind main and overlaps with other lazy-loading approaches (#12379, #13980, #29300). Happy to revisit if maintainers want the session-dedup approach specifically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low — cosmetic, nice to have tool/skills Skills system (list, view, manage) type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants