Skip to content

fix(aistio): dual-name collaboration tools for OpenAI function.name - #3205

Open
tengjiaozhai wants to merge 1 commit into
agentscope-ai:mainfrom
tengjiaozhai:fix/aistio-collaboration-tool-model-names
Open

tengjiaozhai wants to merge 1 commit into
agentscope-ai:mainfrom
tengjiaozhai:fix/aistio-collaboration-tool-model-names

Conversation

@tengjiaozhai

@tengjiaozhai tengjiaozhai commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

AgentScope-Java Version

2.0.3-SNAPSHOT (based on upstream/main at d70910700793329ecd45c927f2a79c282d8bc47e)

Description

OpenAI-compatible APIs require function.name =~ ^[a-zA-Z0-9_-]+$. Collaboration tools were registered with dotted control-plane wire names (task.get, issue.comment.add, …), so every AgentTask model request against OpenAI/DeepSeek-compatible gateways failed with HTTP 400.
original:https://raw.githubusercontent.com/openai/openai-openapi/refs/heads/main/openapi.yaml
image

Option 1 (adapter dual naming) — this PR:

  1. AgentTaskCollaborationTool keeps two names:
    • wireName (dotted): used for tools/call, READ_ONLY, and terminal run.node.complete / run.node.fail checks
    • modelName ('.' → '_' via shared toModelName()): returned from getName() for the LLM / OpenAI payload
  2. HarnessAgentTaskStarter.registerCollaborationTools registers and dedupes by model name; lifecycle skip for task.complete / task.fail uses shared wire-name constants (WIRE_TASK_COMPLETE / WIRE_TASK_FAIL).
  3. roleInstructions (and kickoff / correction prompts) use model-facing underscore names derived from the same toModelName() mapping so prose cannot drift from registration.
  4. AgentTaskOutcomeTool: task.submit_resulttask_submit_result (same mapping). This generalizes / may supersede the manual rename in fix(aistio): rename task.submit_result tool to satisfy OpenAI function-name rules #3151.

Wire dispatch to the control plane is unchanged (still dotted). Option 2 (core Toolkit pattern validation) is left as a follow-up only.

Closes #3153

Relationship to #3151

#3151 only renames the adapter-owned task.submit_result tool. This PR applies the same underscore rule to all collaboration tools plus that outcome tool, so naming stays consistent. If #3151 merges first, this branch should rebase cleanly over the overlapping lines in HarnessAgentTaskStarter / tests; if this lands first, #3151 becomes redundant and can be closed.

Compatibility / release notes

Breaking for callers that hardcode the old outcome tool name: the model-facing name is now task_submit_result (was task.submit_result). Wire/dispatch for dotted collaboration tools is unchanged. Callers, prompts, and resumed sessions that hardcode the old dotted outcome name should use the underscore form.

Verification

mvn -pl agentscope-extensions/agentscope-extensions-aistio -am spotless:apply
mvn -pl agentscope-extensions/agentscope-extensions-aistio spotless:check
mvn -pl agentscope-extensions/agentscope-extensions-aistio -am test
  • agentscope-extensions-aistio: 77 tests, 0 failures (includes new dual-name + terminal-wire coverage, outcome-tool idempotent registration, and collision keep-first pin)
  • Upstream dependency modules in the reactor (core/harness/…): build + tests succeeded as part of -am test

Checklist

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (mvn test): aistio module 77/0
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated (e.g. links, examples, etc.) — N/A (adapter-internal naming; no public docs / CHANGELOG file in repo; documented in Compatibility section above)
  • Code is ready for review

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

Summary

The dual-name design (dotted wire names for MCP dispatch, ._ model names for OpenAI-compatible function.name) is a sound fix for #3153, and the registration / availableActions / terminal-check paths are consistently reworked. However, this cannot be approved in its current state:

Critical: in AgentTaskCollaborationToolTest.discoveresAndCallsTaskScopedMcpTool the escaped quote was dropped from a JSON string literal (+ " a comment","), which leaves the literal unbalanced and makes the test source non-compiling — CI should fail before any of the new assertions run.

Please restore \" on that line and re-run mvn -pl agentscope-extensions/agentscope-extensions-aistio test-compile (the PR reports 76 tests passing, so this looks like it slipped in on the last rebase).

Non-blocking suggestions: an explicit hint about toModelName() collisions (foo.bar_baz vs foo_bar.baz, or a pre-existing task_submit_result shadowing the outcome tool) plus a test pinning that behavior, and a note on documenting the task.submit_result rename in release notes for resumed sessions / external references.

Good first contribution — the fix rationale, shared wire-name constants, and the added dual-name tests are all well done. Once the test file compiles again, re-request review and we can move quickly.


Automated review by github-manager-bot

@tengjiaozhai

Copy link
Copy Markdown
Contributor Author

Soft review items addressed (no further code churn):

  1. PR description — Verification/checklist test counts updated 76 → 77 (collision keep-first pin).
  2. Compatibility / release notes — added a short note that the model-facing outcome name is now task_submit_result (was task.submit_result); wire/dispatch for dotted collaboration tools unchanged; callers/prompts/resumed sessions that hardcode the old dotted name should use the underscore form.
  3. CHANGELOG — repo has no CHANGELOG / RELEASE_NOTES file for user-facing renames, so the PR-body note is the documentation surface for this rename.

Version remains 2.0.3-SNAPSHOT. Code fixes (escaped quote + collision warn) already in 57093bc.

@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...nsions/aistio/adapter/HarnessAgentTaskStarter.java 78.94% 0 Missing and 4 partials ⚠️
...ons/aistio/adapter/AgentTaskCollaborationTool.java 81.81% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

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

Summary

Re-reviewed at 57093bc. Both threads from the previous request are resolved:

  • The dropped \" in the discoversAndCallsTaskScopedMcpTool fixture is restored — the literal is balanced again and CI confirms build (ubuntu/windows-latest) pass on this commit.
  • The model-name collision suggestion was taken further than asked: registerCollaborationTools now logs a warning identifying the skipped wireName/modelName pair, and collidingModelNameKeepsFirstRegisteredTool pins the keep-first semantics (including that the pre-existing local tool is not replaced).

No new issues found in the delta. Nice turnaround — approving.


Automated review by github-manager-bot

@oss-maintainer

Copy link
Copy Markdown
Collaborator

⚠️ Merge conflict detected

This PR currently conflicts with main (mergeable=CONFLICTING), so it cannot be merged even though the code review is done. Please rebase or merge main into your branch and resolve the conflicts:

git fetch origin
git checkout fix/aistio-collaboration-tool-model-names
git rebase origin/main
# resolve conflicts, then:
git push --force-with-lease

This is a one-time reminder. Feel free to @mention me for a re-review once the conflicts are resolved.


Automated notification by github-manager-bot

Rebased onto upstream main (c0d03cc). Keep wireName/modelName dual naming,
collision warn, escaped quote in tests, and main's ConfirmResult denyMessage
HITL path.
@tengjiaozhai
tengjiaozhai force-pushed the fix/aistio-collaboration-tool-model-names branch from 57093bc to f838e87 Compare September 21, 2026 17:02
@tengjiaozhai

Copy link
Copy Markdown
Contributor Author

Rebased onto current upstream main @ c0d03ccea95f6de52b3e1cdfa682af66f06dd705.

  • New head: f838e87aa65ae4fca8e791f82dc93179745137f6
  • Conflicts resolved in HarnessAgentTaskStarter.java and HarnessAgentTaskOutcomeTest.java (kept dual-naming wireName/modelName + collision warn + escaped quote; folded in main’s ConfirmResult(..., denyMessage) HITL path and its test)
  • Local verification: spotless:check clean; agentscope-extensions-aistio 78 tests / 0 failures
  • mergeable is no longer dirty

Ready for re-review, cc @oss-maintainer

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

Summary

Re-review after the rebase onto c0d03ccea. The dual-name design is intact and the conflict resolution looks correct: wireName (dotted) drives MCP dispatch, isReadOnly(), and the terminal-action checks, while getName() now returns the OpenAI-safe modelName; the wire-name constants are used for the task.complete / task.fail exclusion, and ConfirmResult(..., decision.denyMessage()) matches main's current 4-arg HITL signature. The added tests (modelNameUsesUnderscoresWhileWireNameKeepsDots, terminalWireNamesStillTriggerMarkTerminalCommitted, collidingModelNameKeepsFirstRegisteredTool, sameToolkitRegistersOutcomeToolOnlyOnce) cover the right seams, including asserting that params.name on the wire stays dotted.

Not re-approving this round: once normalization is in play, name presence in the toolkit is no longer proof of tool identity, and the outcome tool relies on exactly that proof — plus two compatibility questions created by the rename.

Findings

  • [Critical] adapter/HarnessAgentTaskStarter.java:440 — the contains(submitResultModelName) guard treats any tool named task_submit_result as the adapter's outcome tool. If a foreign tool holds that name, AgentTaskOutcomeTool is never registered, no outcome can be submitted, and runToOutcome spins on its "turn ended without a business outcome" nudge until the budget is exhausted — silently, unlike the collaboration-loop collision which does log. Check identity (getTool(name) instanceof AgentTaskOutcomeTool) instead.
  • [Warning] adapter/AgentTaskCollaborationTool.java:97toModelName normalizes only ., so the ^[a-zA-Z0-9_-]+$ property claimed in the class comment holds just for dotted names; core already has a stricter sanitizer in SubAgentTool.resolveToolName (^[a-zA-Z0-9_-]{1,64}$ with a hash fallback) that would also shrink the collision surface.
  • [Warning] adapter/AgentTaskOutcomeTool.java:32task.submit_resulttask_submit_result breaks persisted permission rules, because PermissionEngine.rulesFor(...) is an exact key lookup on tool.getName() over the allow_rules / deny_rules / ask_rules maps stored in PermissionContextState. Previously-allowed work can re-prompt, or DENY under DONT_ASK. Needs confirmation plus a migration or dual-spelling lookup.
  • [Info] adapter/HarnessAgentTaskStarter.java:464 — a shadowed action is still advertised in availableActions; if a terminal action is the one dropped, the leader stalls. Consider naming dropped pairs in the prompt or refusing the dispatch.
  • [Info] test/HarnessAgentTaskOutcomeTest.java:379 — two more cases worth pinning: outcome-tool shadowing, and a wire name outside [A-Za-z0-9_-].

Notes

  • Static review only; I did not compile or run the module in this sweep (the local mirror could not be synced this cycle), so the CI run remains the authority on build/test status.
  • The roleInstructions string concatenation reads a little awkwardly after interpolation (e.g. " also" + " completes ..."), but the output is correct and the new assertFalse(...contains("run.node.complete")) assertions are a nice guard against the dotted spelling leaking back into prompts.

Automated review by github-manager-bot

String submitResultModelName =
AgentTaskCollaborationTool.toModelName(
AgentTaskCollaborationTool.WIRE_TASK_SUBMIT_RESULT);
if (!runtimeAgent.getToolkit().getToolNames().contains(submitResultModelName)) {

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.

After the '.' -> '_' normalization, this guard assumes "a tool named task_submit_result already in the toolkit must be our outcome tool". That assumption is not safe — a foreign tool can occupy the same model name, which is exactly the situation the new collidingModelNameKeepsFirstRegisteredTool test demonstrates for task_get.

If it happens here, AgentTaskOutcomeTool is never registered, no business outcome can ever be submitted, and runToOutcome keeps emitting its "The turn ended without a business outcome" nudge until the turn budget is exhausted. The collaboration-tool loop below at least logs a warning on collision; this path is silent.

Suggest matching on identity rather than name, e.g. getTool(submitResultModelName) instanceof AgentTaskOutcomeTool, and either replacing the shadowed tool or failing the dispatch loudly — this is the one model-facing name the completion protocol cannot do without.

if (wireName == null) {
return null;
}
return wireName.replace('.', '_');

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.

Only . is normalized, while the class comment states the constraint as function.name =~ ^[a-zA-Z0-9_-]+$. Wire names containing any other character outside that class (:, /, whitespace, non-ASCII) pass through unchanged, so the "OpenAI-safe" property holds only for the dotted-names case the control plane happens to publish today; length is also unbounded.

Core already has a sanitizer for the same rule (SubAgentTool.resolveToolName, documented as ^[a-zA-Z0-9_-]{1,64}$ with a deterministic-hash fallback). Reusing it here — or asserting the regex and falling back to a stable suffixed name — would make the mapping total instead of case-specific, and would also remove the collision surface this PR had to add a warning for.

public final class AgentTaskOutcomeTool {
@Tool(
name = "task.submit_result",
name = "task_submit_result",

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.

Renaming the model-facing tool from task.submit_result to task_submit_result also invalidates persisted permission rules: PermissionEngine.rulesFor(...) does an exact key lookup on tool.getName() against the allow/deny/ask tables, and those tables are persisted in PermissionContextState (allow_rules / deny_rules / ask_rules). A session or stored config that granted ALLOW for the dotted spelling stops matching, so previously-approved work re-prompts — or, under DONT_ASK, falls through to DENY.

Could you confirm nothing deployed references the dotted name (session stores, seeded rules, prompt templates), and either migrate those entries or accept both spellings during rule lookup? Worth one line in the docs/release note either way.

new AgentTaskCollaborationTool(collaboration, definition));
} else {
LOG.warning(
"Skipping collaboration tool registration due to model-name collision:"

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.

Logging collisions is the right call. One gap remains: the dropped action is still advertised by availableActions, so the only signal the model gets is the Actual tool names available to this agent: line at the end of the prompt. If a terminal action (run.node.complete / run.node.fail) is the one shadowed, a leader can never converge and the run just stalls.

Consider naming the dropped wire/model pairs explicitly in the prompt, or refusing the dispatch when a terminal action cannot be exposed.

}

@Test
void collidingModelNameKeepsFirstRegisteredTool() throws Exception {

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.

Good coverage of the rebase follow-ups — the local-collision case and the idempotent re-dispatch case are both pinned here. Two more tests would lock down the risky paths flagged inline above:

  • outcome-tool shadowing: pre-register a foreign tool under task_submit_result and assert the dispatch either still captures a business outcome or fails loudly;
  • a wire name outside [A-Za-z0-9_-] (e.g. mcp:server.tool), so the normalization contract is specified rather than implied by the dotted-name fixture.

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.

aistio: collaboration tool names use dots and still break OpenAI-compatible models (HTTP 400)

2 participants