Skip to content

fix: 同步动态 skill tool 激活状态#1735

Open
guslegend0510 wants to merge 1 commit into
agentscope-ai:mainfrom
guslegend0510:fix/issue-1715-skill-tools
Open

fix: 同步动态 skill tool 激活状态#1735
guslegend0510 wants to merge 1 commit into
agentscope-ai:mainfrom
guslegend0510:fix/issue-1715-skill-tools

Conversation

@guslegend0510

Copy link
Copy Markdown

问题背景

Issue #1715 中,动态 skill 在两处会出现工具不可见的问题:

  1. onSystemPrompt 阶段新注册或激活的 tool group 没有同步到当前 call state,导致首轮 reasoning 看不到动态工具。
  2. load_skill_through_path 执行后,toolkit 中对应的 skill tool group 已经激活,但 ReActAgent 下一轮 reasoning 仍然读取旧的 state active groups,导致 skill-bound tools 仍不可见。

变更内容

  1. ReActAgent 中补充 toolkit active groups 到当前 call state 的同步时机:
    • consumeSystemMsgAfterPreCall(...) 后同步一次,保证首轮 reasoning 可见;
    • acting 阶段工具执行完成后再次同步,保证下一轮 reasoning 可见。
  2. 在 harness 侧补齐 skill load 后的 tool group 激活逻辑:
    • SkillLoadTool 成功加载 skill 后,优先使用 param.getAgent().getToolkit(),否则 fallback 到 runtime 持有的 toolkit;
    • 如果存在 <skillId>_skill_tools,则立即激活对应 group。
  3. SkillRuntime.install(...) 中,每次安装时刷新 runtime 持有的 toolkit 引用。
  4. 补充回归测试,覆盖:
    • onSystemPrompt 激活的 group 在首轮 reasoning 可见;
    • tool 执行后激活的 group 在下一轮 reasoning 可见;
    • harness 中 load skill 后会激活对应 skill tool group,且 toolkit fallback 引用会刷新。

验证

已执行并通过以下测试:

  • mvn -pl agentscope-core '-Dtest=ReActAgentToolGroupSyncTest' '-Dsurefire.failIfNoSpecifiedTests=false' test
  • mvn -pl agentscope-core '-Dtest=SkillRuntimeIntegrationTest,ReActAgentMiddlewareIntegrationTest' '-Dsurefire.failIfNoSpecifiedTests=false' test
  • mvn -pl agentscope-harness -am '-Dtest=SkillRuntimeTest' '-Dsurefire.failIfNoSpecifiedTests=false' test
  • mvn -pl agentscope-harness -am '-Dtest=SkillLoadToolFrontmatterViewTest' '-Dsurefire.failIfNoSpecifiedTests=false' test

@guslegend0510 guslegend0510 requested a review from a team June 12, 2026 10:37
@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.48148% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ope/harness/agent/skill/runtime/SkillLoadTool.java 75.00% 2 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

@AgentScopeJavaBot AgentScopeJavaBot added bug Something isn't working area/core/agent Agent runtime, pipeline, hooks, plan area/harness agentscope-harness (test/runtime support) labels Jun 13, 2026

@AgentScopeJavaBot AgentScopeJavaBot left a comment

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.

🤖 AI Review

This PR fixes a real and well-identified issue: dynamic skill tool groups activated during onSystemPrompt middleware or tool execution were not visible to the current/next reasoning round because the call-scoped AgentState was stale relative to the live Toolkit. The fix adds two strategically placed syncToolkitToState() calls (after system prompt consumption and after acting) and introduces tool group activation logic in SkillLoadTool with a sensible fallback chain (param.getAgent().getToolkit()toolkitRef). The SkillRuntime.install() now refreshes the toolkitRef to prevent stale references. Test coverage is solid, covering all three sync scenarios. The implementation is clean, minimal, and correctly handles null safety throughout. No blocking issues found.

if (toolkit == null) {
return;
}
String toolGroupName = entry.skill().getSkillId() + "_skill_tools";

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] The _skill_tools suffix is duplicated here — the same convention is already encapsulated in RegisteredSkill.getToolsGroupName() (and used inline in SkillBox). Consider extracting it to a shared constant (e.g. AgentSkill.TOOL_GROUP_SUFFIX = "_skill_tools") to avoid silent drift if the naming convention ever changes.

if (toolkit == null && toolkitRef != null) {
toolkit = toolkitRef.get();
}
if (toolkit == null) {

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] When the toolkit cannot be resolved (both param.getAgent() and toolkitRef fallback return null), the method returns silently. Consider adding a log.debug("activateSkillTools: no toolkit available for skill {}", entry.skill().getSkillId()) here to aid production debugging of skill activation issues.

scope.systemMsg = systemMsg;
// onSystemPrompt middleware can mutate the live toolkit before the first reasoning
// round, so mirror those active-group changes into the call-scoped AgentState now.
syncToolkitToState(scope.state);

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.

[praise] Good placement — syncing the toolkit's active groups into the call-scoped state right after onSystemPrompt middleware has had a chance to mutate the toolkit ensures the first reasoning round sees dynamically registered tools. The comment clearly explains the rationale.

// (for example, a skill loader exposing newly bound tools).
// Persist that live toolkit state before any branch resumes
// reasoning or returns early.
syncToolkitToState(state);

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.

[praise] Correct positioning at the top of the results flatMap, before any branching (stop/suspended/continue). This ensures the state is synced regardless of which exit path is taken, and it happens after all tool executions have completed — so any activateSkillTools calls during acting are properly captured.

@AgentScopeJavaBot AgentScopeJavaBot left a comment

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.

🤖 AI Review

This PR fixes a real and well-identified issue: dynamic skill tool groups activated during onSystemPrompt middleware or tool execution were not visible to the current/next reasoning round because the call-scoped AgentState was stale relative to the live Toolkit. The fix adds two strategically placed syncToolkitToState() calls (after system prompt consumption and after acting) and introduces tool group activation logic in SkillLoadTool with a sensible fallback chain (param.getAgent().getToolkit()toolkitRef). The SkillRuntime.install() now refreshes the toolkitRef to prevent stale references. Test coverage is solid, covering all three sync scenarios. The implementation is clean, minimal, and correctly handles null safety throughout. No blocking issues found.

if (toolkit == null) {
return;
}
String toolGroupName = entry.skill().getSkillId() + "_skill_tools";

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] The _skill_tools suffix is duplicated here — the same convention is already encapsulated in RegisteredSkill.getToolsGroupName() (and used inline in SkillBox). Consider extracting it to a shared constant (e.g. AgentSkill.TOOL_GROUP_SUFFIX = "_skill_tools") to avoid silent drift if the naming convention ever changes.

if (toolkit == null && toolkitRef != null) {
toolkit = toolkitRef.get();
}
if (toolkit == null) {

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] When the toolkit cannot be resolved (both param.getAgent() and toolkitRef fallback return null), the method returns silently. Consider adding a log.debug("activateSkillTools: no toolkit available for skill {}", entry.skill().getSkillId()) here to aid production debugging of skill activation issues.

scope.systemMsg = systemMsg;
// onSystemPrompt middleware can mutate the live toolkit before the first reasoning
// round, so mirror those active-group changes into the call-scoped AgentState now.
syncToolkitToState(scope.state);

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.

[praise] Good placement — syncing the toolkit's active groups into the call-scoped state right after onSystemPrompt middleware has had a chance to mutate the toolkit ensures the first reasoning round sees dynamically registered tools. The comment clearly explains the rationale.

// (for example, a skill loader exposing newly bound tools).
// Persist that live toolkit state before any branch resumes
// reasoning or returns early.
syncToolkitToState(state);

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.

[praise] Correct positioning at the top of the results flatMap, before any branching (stop/suspended/continue). This ensures the state is synced regardless of which exit path is taken, and it happens after all tool executions have completed — so any activateSkillTools calls during acting are properly captured.

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

Labels

area/core/agent Agent runtime, pipeline, hooks, plan area/harness agentscope-harness (test/runtime support) bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants