diff --git a/agentscope-extensions/agentscope-extensions-aistio/src/main/java/io/agentscope/extensions/aistio/adapter/AgentTaskOutcomeTool.java b/agentscope-extensions/agentscope-extensions-aistio/src/main/java/io/agentscope/extensions/aistio/adapter/AgentTaskOutcomeTool.java index 6d40a491e4..1421e9c5d4 100644 --- a/agentscope-extensions/agentscope-extensions-aistio/src/main/java/io/agentscope/extensions/aistio/adapter/AgentTaskOutcomeTool.java +++ b/agentscope-extensions/agentscope-extensions-aistio/src/main/java/io/agentscope/extensions/aistio/adapter/AgentTaskOutcomeTool.java @@ -29,7 +29,7 @@ /** Captures intent. Only the adapter is allowed to commit the physical AgentTask lifecycle. */ public final class AgentTaskOutcomeTool { @Tool( - name = "task.submit_result", + name = "task_submit_result", description = "Submit the actual outcome of this AgentTask, then end your turn. succeeded" + " requires the full deliverable in result, not a plan or promise. waiting" diff --git a/agentscope-extensions/agentscope-extensions-aistio/src/main/java/io/agentscope/extensions/aistio/adapter/HarnessAgentTaskStarter.java b/agentscope-extensions/agentscope-extensions-aistio/src/main/java/io/agentscope/extensions/aistio/adapter/HarnessAgentTaskStarter.java index c7d6bac145..be152d9f2f 100644 --- a/agentscope-extensions/agentscope-extensions-aistio/src/main/java/io/agentscope/extensions/aistio/adapter/HarnessAgentTaskStarter.java +++ b/agentscope-extensions/agentscope-extensions-aistio/src/main/java/io/agentscope/extensions/aistio/adapter/HarnessAgentTaskStarter.java @@ -132,7 +132,7 @@ private void execute(AgentTaskAssignment assignment) { + " available CollaborationClient actions are registered as tools with" + " the exact names shown in availableActions. The adapter owns" + " task.complete and task.fail; do not call them. Before returning," - + " call task.submit_result with an explicit business outcome and the" + + " call task_submit_result with an explicit business outcome and the" + " actual deliverable. A promise to do work later is not completion." + " Check the tool capabilities before delegating: spawning a subagent" + " does not add missing web access." @@ -259,7 +259,7 @@ private AgentTaskOutcome runToOutcome( next = message( "The turn ended without a business outcome. Do the remaining work," - + " or call task.submit_result with blocked and the concrete" + + " or call task_submit_result with blocked and the concrete" + " missing capability. Do not submit a plan or waiting promise" + " as successful research."); continue; @@ -433,7 +433,7 @@ private void registerCollaborationTools( Set availableActions) { Object toolkit = runtimeAgent.getToolkit(); synchronized (toolkit) { - if (!runtimeAgent.getToolkit().getToolNames().contains("task.submit_result")) { + if (!runtimeAgent.getToolkit().getToolNames().contains("task_submit_result")) { runtimeAgent.getToolkit().registerTool(new AgentTaskOutcomeTool()); } for (JsonNode definition : @@ -466,12 +466,12 @@ static String roleInstructions(JsonNode envelope, List inputIds) { return " You are a Team worker, not its coordinator. Do not create or accept child" + " Issues and do not call run.node.complete, run.node.fail, or run.replan." + " Complete only the assigned work and submit its result using" - + " task.submit_result; the adapter will complete this AgentTask."; + + " task_submit_result; the adapter will complete this AgentTask."; } if (inputIds.isEmpty()) { return " You are the Team leader's initial task. If you delegate child work, return" + " immediately after issue.child.create succeeds by calling" - + " task.submit_result with waiting, a reason and the returned AgentTask" + + " task_submit_result with waiting, a reason and the returned AgentTask" + " IDs; do not wait through local session/task tools and do not call" + " run.node.complete yet. The control plane will deliver a fresh leader" + " follow-up when a worker result arrives. If no work is delegated, call" diff --git a/agentscope-extensions/agentscope-extensions-aistio/src/test/java/io/agentscope/extensions/aistio/adapter/AgentTaskCollaborationToolTest.java b/agentscope-extensions/agentscope-extensions-aistio/src/test/java/io/agentscope/extensions/aistio/adapter/AgentTaskCollaborationToolTest.java index 5886cca97f..cddedba044 100644 --- a/agentscope-extensions/agentscope-extensions-aistio/src/test/java/io/agentscope/extensions/aistio/adapter/AgentTaskCollaborationToolTest.java +++ b/agentscope-extensions/agentscope-extensions-aistio/src/test/java/io/agentscope/extensions/aistio/adapter/AgentTaskCollaborationToolTest.java @@ -59,9 +59,15 @@ void roleInstructionsSeparateLeaderHandoffFromWorkerExecution() throws Exception assertTrue( HarnessAgentTaskStarter.roleInstructions(worker, List.of()) .contains("Team worker, not its coordinator")); + assertTrue( + HarnessAgentTaskStarter.roleInstructions(worker, List.of()) + .contains("task_submit_result")); assertTrue( HarnessAgentTaskStarter.roleInstructions(leader, List.of()) .contains("return immediately after issue.child.create succeeds")); + assertTrue( + HarnessAgentTaskStarter.roleInstructions(leader, List.of()) + .contains("task_submit_result with waiting")); String followUp = HarnessAgentTaskStarter.roleInstructions(leader, List.of("input-1")); assertTrue(followUp.contains("leader follow-up")); assertTrue(followUp.contains("Never send those mutations in parallel")); diff --git a/agentscope-extensions/agentscope-extensions-aistio/src/test/java/io/agentscope/extensions/aistio/adapter/HarnessAgentTaskOutcomeTest.java b/agentscope-extensions/agentscope-extensions-aistio/src/test/java/io/agentscope/extensions/aistio/adapter/HarnessAgentTaskOutcomeTest.java index 206489e98b..d2b6baf5be 100644 --- a/agentscope-extensions/agentscope-extensions-aistio/src/test/java/io/agentscope/extensions/aistio/adapter/HarnessAgentTaskOutcomeTest.java +++ b/agentscope-extensions/agentscope-extensions-aistio/src/test/java/io/agentscope/extensions/aistio/adapter/HarnessAgentTaskOutcomeTest.java @@ -78,6 +78,54 @@ private HarnessAgentTaskStarter starter() throws Exception { return new HarnessAgentTaskStarter(() -> agent, client); } + @Test + void sameToolkitRegistersOutcomeToolOnlyOnce() throws Exception { + var starter = starter(); + when(agent.call(any(Msg.class), any(RuntimeContext.class))) + .thenAnswer( + invocation -> { + RuntimeContext ctx = invocation.getArgument(1); + ctx.get(AgentTaskOutcome.State.class) + .submit( + new AgentTaskOutcome( + "succeeded", "delivered", "", List.of())); + return Mono.just( + Msg.builder() + .role(MsgRole.ASSISTANT) + .textContent("done") + .build()); + }); + starter.start(assignment).block(); + // A second dispatch on the same toolkit must hit the "already registered" branch of + // registerCollaborationTools and still complete normally. + AgentTaskAssignment second = + new AgentTaskAssignment( + "attempt-2", + "task", + "run", + "node", + 1, + "dispatch", + "", + "secret-token", + "attempt-secret", + "assigned-session", + new byte[0], + 1); + starter.start(second).block(); + verify(agent, times(2)).call(any(Msg.class), any(RuntimeContext.class)); + verify(client, times(2)) + .finish( + eq("task"), + eq("secret-token"), + eq(4L), + eq("succeeded"), + eq(""), + eq("delivered"), + eq(List.of()), + eq(List.of())); + } + @Test void plainWaitingPromiseCannotBecomeSuccessfulBusinessCompletion() throws Exception { var starter = starter();