Skip to content

refactor(tool): add Toolkit deep copy to isolate agent state#438

Merged
AlbumenJ merged 4 commits intoagentscope-ai:mainfrom
AlbumenJ:0104-state
Jan 4, 2026
Merged

refactor(tool): add Toolkit deep copy to isolate agent state#438
AlbumenJ merged 4 commits intoagentscope-ai:mainfrom
AlbumenJ:0104-state

Conversation

@AlbumenJ
Copy link
Collaborator

@AlbumenJ AlbumenJ commented Jan 4, 2026

No description provided.

Change-Id: I33a8687ea436db43e9f0a8af763496747a5f9526
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds deep copy functionality to the Toolkit class to enable agent state isolation. Each agent built from a ReActAgent.Builder now receives its own deep copy of the toolkit, preventing state interference when multiple agents share the same toolkit configuration.

  • Deep copy implementation for Toolkit, ToolRegistry, and ToolGroupManager
  • ReActAgent builder now creates toolkit copies per agent instance
  • Builder collections changed to Sets (hooks, knowledgeBases) for deduplication
  • Integration of ActingChunkEvent notification for streaming tool execution

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
Toolkit.java Adds copy() method to create deep copies of toolkit instances for agent isolation
ToolRegistry.java Implements copyTo() to transfer tool registrations between registries
ToolGroupManager.java Implements copyTo() to duplicate tool group state and active group tracking
ReActAgent.java Builder creates toolkit deep copy per agent; adds chunk callback for streaming; changes hooks/knowledgeBases to Sets

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

String toolName = entry.getKey();
AgentTool tool = entry.getValue();
RegisteredToolFunction registered = registeredTools.get(toolName);
target.registerTool(toolName, tool, registered);
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The copyTo method performs a shallow copy by sharing the same AgentTool and RegisteredToolFunction instances between the source and target registries. This violates the deep copy intention stated in the PR.

RegisteredToolFunction has mutable state (the presetParameters field can be modified via updatePresetParameters method). When agents share the same RegisteredToolFunction instance, calling toolkit.updateToolPresetParameters on one agent's toolkit will affect other agents that share the same tool reference.

To properly isolate agent state, each RegisteredToolFunction should be deep copied with its own presetParameters map. Consider creating a copy constructor or copy method in RegisteredToolFunction that creates a new instance with copied state.

Suggested change
target.registerTool(toolName, tool, registered);
RegisteredToolFunction registeredCopy = registered == null ? null : registered.copy();
target.registerTool(toolName, tool, registeredCopy);

Copilot uses AI. Check for mistakes.
Comment on lines +652 to +662
public Toolkit copy() {
Toolkit copy = new Toolkit(this.config);

// Copy all registered tools
this.toolRegistry.copyTo(copy.toolRegistry);

// Copy all tool groups and their states
this.groupManager.copyTo(copy.groupManager);

return copy;
}
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The new copy() method lacks test coverage. Given the critical nature of this functionality for agent state isolation and the comprehensive test suite for Toolkit (ToolkitTest.java has 740 lines of tests), this method should have corresponding tests.

Suggested test scenarios:

  1. Verify that modifying copied toolkit's tool groups doesn't affect the original
  2. Verify that updating preset parameters in one toolkit doesn't affect the copy
  3. Verify that all tools are copied correctly
  4. Verify that active groups state is properly isolated
  5. Verify that the config is shared (not copied) as intended

Copilot uses AI. Check for mistakes.
}

// Set chunk callback for streaming tool responses
toolkit.setChunkCallback((toolUse, chunk) -> notifyActingChunk(toolUse, chunk).subscribe());
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The chunk callback uses .subscribe() which creates a fire-and-forget subscription. This means:

  1. Any errors in notifyActingChunk will be silently swallowed
  2. The subscription is not managed or cancelled, potentially leading to resource leaks
  3. There's no backpressure handling

Consider returning the Mono without subscribing and let the caller manage the subscription, or use .subscribeOn() with proper error handling. Alternatively, if fire-and-forget is intentional, add explicit error handling with .doOnError().

Suggested change
toolkit.setChunkCallback((toolUse, chunk) -> notifyActingChunk(toolUse, chunk).subscribe());
toolkit.setChunkCallback(
(toolUse, chunk) ->
notifyActingChunk(toolUse, chunk)
.doOnError(
error ->
LoggerFactory.getLogger(ReActAgent.class)
.error(
"Error while notifying acting chunk",
error))
.subscribe());

Copilot uses AI. Check for mistakes.
Change-Id: I5992bcaa9cec12061cc8a03b717828f893ebd91f
Change-Id: If8b985c041e0311492a7b871d655316cd2056ef2
Change-Id: Id4921c309801b0e4e0d82eed6af5b68888609230
@codecov
Copy link

codecov bot commented Jan 4, 2026

Codecov Report

❌ Patch coverage is 54.76190% with 19 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...java/io/agentscope/core/tool/ToolGroupManager.java 15.38% 10 Missing and 1 partial ⚠️
...e/src/main/java/io/agentscope/core/ReActAgent.java 55.55% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@AlbumenJ AlbumenJ merged commit 661472f into agentscope-ai:main Jan 4, 2026
3 of 4 checks passed
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