Skip to content

test(agent): regression coverage for #2169 skill load tool on fresh sessions - #2369

Open
gxgeek-n wants to merge 2 commits into
agentscope-ai:mainfrom
gxgeek-n:test/2169-skill-load-fresh-session
Open

test(agent): regression coverage for #2169 skill load tool on fresh sessions#2369
gxgeek-n wants to merge 2 commits into
agentscope-ai:mainfrom
gxgeek-n:test/2169-skill-load-fresh-session

Conversation

@gxgeek-n

Copy link
Copy Markdown
Contributor

Summary

Adds end-to-end regression tests pinning the fix for #2169 (Unauthorized tool call: 'load_skill_through_path' is not available on fresh sessions).

The bug: on the first call of a fresh session, activateSlotForContext applied the state's (empty) activated group list via setActiveGroups, deactivating the build-time active skill-build-in-tools group. SkillHook kept advertising skills in the system prompt, so the model inevitably called load_skill_through_path and got the Unauthorized error. This was fixed on main by #2319 (fresh slots inherit build-time active groups), but the end-to-end path through a real SkillBox + ReActAgent call had no coverage — existing tests only assert state seeding with manually-created groups, and SkillBoxTest/SkillBoxToolsTest never drive a full agent call.

What the tests do

SkillLoadToolFreshSessionTest drives a scripted model through a real ReAct loop with a registered skill, asserting on the tool result exactly as the model observes it:

  1. freshSession_skillLoadToolExecutes — the load tool executes and returns the skill content (not Unauthorized)
  2. freshSession_stateSeedsSkillGroupToolContextState keeps skill-build-in-tools activated after the call
  3. secondCall_skillLoadToolStillAuthorized — activation survives repeated calls in one session (the wipe happened per call, not just on the first)

Verified both directions

# v2.0.0
Tests run: 3, Failures: 3 — Unauthorized tool call: 'load_skill_through_path' is not available
# main
Tests run: 3, Failures: 0, Errors: 0 — BUILD SUCCESS

Notes

Copilot AI review requested due to automatic review settings July 23, 2026 12:13
@CLAassistant

CLAassistant commented Jul 23, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds end-to-end regression coverage for #2169 by exercising a full ReAct loop with a real SkillBox + ReActAgent, asserting that load_skill_through_path remains authorized on fresh sessions and across repeated calls.

Changes:

  • Introduces SkillLoadToolFreshSessionTest with scripted-model e2e tests to ensure the skill load tool executes (not Unauthorized) on the first call of a fresh session.
  • Verifies ToolContextState retains the build-time active skill-build-in-tools group after the call.
  • Verifies authorization persists across a second call within the same session.

Comment on lines +91 to +96
// Registered skill ids carry a "_custom" source suffix (see RegisteredSkill);
// the load tool's skillId enum only accepts the registered form.
String registeredSkillId = SKILL_ID + "_custom";
Map<String, Object> input = new HashMap<>();
input.put("skillId", registeredSkillId);
input.put("path", "SKILL.md");
…ol on fresh sessions

Add end-to-end tests pinning the fix for agentscope-java#2169: a fresh-session
ReActAgent built with a SkillBox must be able to execute
load_skill_through_path instead of receiving
"Unauthorized tool call: 'load_skill_through_path' is not available".

The tests drive a scripted model through a real ReAct loop with a registered
skill and assert on the tool result exactly as the model observes it:

- fresh session: the load tool executes and returns the skill content
- fresh session: ToolContextState keeps skill-build-in-tools activated
- second call in the same session: the load tool stays authorized

Verified both directions: all three tests fail on the v2.0.0 tag with the
exact "Unauthorized tool call" error from the issue, and pass on main with
the fresh-slot group seeding from agentscope-ai#2319.
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@gxgeek-n
gxgeek-n force-pushed the test/2169-skill-load-fresh-session branch from 3ceaf28 to 0227ef5 Compare July 23, 2026 12:27
@oss-maintainer

This comment was marked as abuse.

@oss-maintainer oss-maintainer 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.

LGTM. Looks good.


Automated review by "github-manager-bot"

@gxgeek-n

Copy link
Copy Markdown
Contributor Author

Friendly ping — this one is test-only and looks ready to merge:

  • All 6 status checks are green, and codecov/patch reports all modified and coverable lines are covered.
  • oss-maintainer has already left an APPROVED review.
  • Note on the CLA comment above: the "CLA Not Signed" message from oss-maintainer is stale. The actual commit status is license/cla: success — Contributor License Agreement is signed. — the CLA is not blocking.

Scope reminder: this PR adds no production code, only end-to-end regression coverage for #2169 — it drives a real SkillBox + ReActAgent through a full ReAct loop and asserts load_skill_through_path stays authorized on fresh sessions and across repeated calls.

Why it's worth landing alongside the fix: #2169 was a deadlock where the unlock tool itself got gated on a fresh session — the fix in a7008d05 (initialActiveToolGroups) is easy to regress on any future change to tool-group activation, and unit tests alone don't catch it because the failure only shows up through the full loop.

I checked the 15 commits merged into main since this was opened — none touch skill-related files, so no conflict. Currently 15 commits behind main; happy to rebase if that helps.

@gxgeek-n

Copy link
Copy Markdown
Contributor Author

Thanks @copilot — I checked this against the source, and the _custom suffix is correct here. Detail below in case it is useful for others reading the test.

AgentSkill.getSkillId() composes the id from name and source:

// AgentSkill.java:267
public String getSkillId() {
    return getName() + "_" + source;
}

and the 4-arg constructor the test uses defaults source to "custom":

// AgentSkill.java:94
public AgentSkill(String name, String description, String skillContent, Map<String, String> resources) {
    this(name, description, skillContent, resources, "custom");   // ← source
}

So new AgentSkill("demo_skill", ...) registers under skill id demo_skill_custom, and the skillId enum in SkillToolFactory is populated from getSkillId() — not the raw name. The scripted call therefore targets a skill that does exist.

Worth noting the suffix is not a hard-coded "_custom" string anywhere (grepping for it across agentscope-core and agentscope-harness returns nothing) — it is assembled from the default source, which is easy to miss.

The test also asserts positively rather than just "no error":

assertTrue(model.observedToolResult.contains(SKILL_BODY), ...)

If the load had returned "Skill not found", observedToolResult would carry the error text and not SKILL_BODY, so the assertion would fail. The suite is green (3/3), which confirms the intended authorization path is exercised.

That said, your comment points at a real readability problem: the test comment says the suffix comes from RegisteredSkill, which is the wrong reference. I will correct it to point at AgentSkill.getSkillId() / the default source so the next reader does not have to derive it. Let me know if you would rather the test construct the id via skill.getSkillId() instead of writing the literal — that would make it self-documenting and immune to a future change in the composition rule.

…custom"

Review feedback flagged the hard-coded "_custom" suffix as likely wrong. It was
correct, but for a non-obvious reason and the comment cited the wrong class:
AgentSkill.getSkillId() composes name + "_" + source, and the 4-arg constructor
defaults source to "custom".

Now the skill is a shared constant and the id comes from getSkillId(), so the
test follows any change to that composition rule and no longer needs a reader
to derive where the suffix comes from.

3 tests, 0 failures.
@gxgeek-n

Copy link
Copy Markdown
Contributor Author

Pushed the readability fix I mentioned — went with the self-documenting option:

private static final AgentSkill DEMO_SKILL =
        new AgentSkill(SKILL_ID, "Demo Skill", SKILL_BODY, new HashMap<>());

/** AgentSkill.getSkillId() composes name + "_" + source; the 4-arg ctor defaults source to "custom". */
private static final String REGISTERED_SKILL_ID = DEMO_SKILL.getSkillId();

The literal "_custom" is gone, the wrong RegisteredSkill reference is corrected, and the same instance is now used for both registration and the scripted call — so if the id composition rule ever changes, the test follows it instead of silently targeting a stale id.

3/3 still green.

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.

4 participants