Skip to content

Dynamically refresh skill instructions each turn - #9217

Merged
DOsinga merged 3 commits into
mainfrom
fix/hot-reload-skills-6382
May 14, 2026
Merged

Dynamically refresh skill instructions each turn#9217
DOsinga merged 3 commits into
mainfrom
fix/hot-reload-skills-6382

Conversation

@DOsinga

@DOsinga DOsinga commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Skills listed in the system prompt were computed once at session start and cached in server_info. Adding or modifying SKILL.md files mid-session had no effect on the system prompt until restart.

Changes

  • McpClientTrait: Add get_instructions() with a default implementation that reads from get_info(). This is a non-breaking addition — all existing implementors get the default.
  • SkillsClient: Override get_instructions() to re-discover skills from disk on every call. The constructor no longer bakes instructions into InitializeResult.
  • Extension::get_instructions(): Delegate to self.client.get_instructions() instead of reading from the cached server_info snapshot.

The system prompt is rebuilt each turn, and get_extensions_info() calls Extension::get_instructions() each time — so newly added, modified, or removed skills are now reflected immediately without session restart.

This is safe for non-skill platform extensions and external MCP servers: they use the default get_instructions() which reads from their static get_info(), preserving existing behavior.

Closes #6382

Skills listed in the system prompt were computed once at session start
and cached. Adding or modifying SKILL.md files mid-session had no
effect on the system prompt until restart.

Add a get_instructions() method to McpClientTrait with a default that
reads from get_info(). SkillsClient overrides it to re-discover skills
from disk on every call. Extension::get_instructions() now delegates to
the client instead of reading the cached server_info snapshot.

Closes #6382

Signed-off-by: Douwe Osinga <douwe@squareup.com>

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

Copy link
Copy Markdown

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: 5b359c2797

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 108 to +109
fn get_instructions(&self) -> Option<String> {
self.server_info
.as_ref()
.and_then(|info| info.instructions.clone())
self.client.get_instructions()

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 Avoid rescanning skills synchronously on each prompt turn

Delegating Extension::get_instructions() to client.get_instructions() now makes the skills extension run discover_skills() on every get_extensions_info() call (which happens whenever the system prompt is rebuilt each turn). discover_skills() performs recursive std::fs walking, so this introduces blocking filesystem I/O in the hot path and can noticeably increase response latency when users have many skills/supporting files. Consider caching/invalidation or moving the scan off the async worker path.

Useful? React with 👍 / 👎.

Comment thread crates/goose/src/skills/client.rs Outdated
}

fn get_instructions(&self) -> Option<String> {
let instructions = build_skill_instructions(&self.working_dir);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: i would inline the impl of build_skill_instructions here as it's only called once

Douwe Osinga added 2 commits May 14, 2026 15:12
MockClient had todo!() in get_info(). Now that Extension::get_instructions()
delegates to the client's get_instructions() default (which calls get_info()),
this panic was hit during scenario tests. Return None instead.

Signed-off-by: Douwe Osinga <douwe@squareup.com>
Signed-off-by: Douwe Osinga <douwe@squareup.com>

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

Copy link
Copy Markdown

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: 4341f0e6d4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

fn get_instructions(&self) -> Option<String> {
let sources = discover_skills(Some(&self.working_dir));

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 Skip skill discovery when session context is missing

get_instructions() now always scans self.working_dir, but SkillsClient::new() falls back to std::env::current_dir() when no session is attached. In production, extensions can be added with session_id: None (for example via the extension-manager tool path), so this change can inject skills from the process CWD instead of the active session/project directory into the system prompt. Before this commit, instructions were not emitted in the no-session case, so this is a behavioral regression that can surface incorrect or unrelated skills.

Useful? React with 👍 / 👎.

@DOsinga
DOsinga added this pull request to the merge queue May 14, 2026
Merged via the queue into main with commit 401f8e8 May 14, 2026
22 of 23 checks passed
@DOsinga
DOsinga deleted the fix/hot-reload-skills-6382 branch May 14, 2026 20:24
lifeizhou-ap added a commit that referenced this pull request May 15, 2026
* main: (102 commits)
  Dynamically refresh skill instructions each turn (#9217)
  Build non-vulkan linux variants using ubuntu 22.04 (#9211)
  fix(ui): show tool name in approval prompt (#9216)
  feat: add Atomic Chat as declarative OpenAI-compatible provider (#9210)
  chore: bump package.json versions from 0.19.1 to 0.20.0 (#9218)
  feat: support GOOSE_OAUTH_CALLBACK_PORT for stable OAuth redirect_uri (#9209)
  [RFC] feat(oauth): proactive token refresh to avoid re-auth on every session (#8386)
  fix: resolve Azure CLI on Windows by using az.cmd (#9215)
  fix: handle non-interactive terminal in goose configure on Windows (#9214)
  Better parsing of pasted html as markdown so agents understand (#9190)
  fix: persist accumulated cost in session DB to survive reload (#9191)
  fix(publish-npm): build binary from current SHA + add compat check (#9212)
  feat(desktop): add goose://new-session deep link to open fresh chat (#9196)
  Add PR previews using cloudflare pages (#9208)
  fix: prevent tool-use marker leakage in toolshim output (#8310)
  Prompt injection mitigation: update pattern-based detection (#9198)
  remove goose2 related skills (#9189)
  Switch GH pages deploy to actions/artifact workflow (#9025)
  fix(summon): re-apply canonical limits when delegate overrides model (#9183)
  Split code signing from build (#8587)
  ...
shafqatevo pushed a commit to shafqatevo/goose that referenced this pull request Aug 7, 2026
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
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.

Feature Request: Hot Reload Skills

2 participants