agentscope-ai / agentscope-ai/agentscope

[Bug]: DeveloperOrientedException can emit an empty success and leave tool calls unfinished

Open
#2,618 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
31.6k
Forks
3.5k
Avg merge
1d 16h
Merged PRs (30d)
103

Description

### Prerequisites

- [x] I have searched the existing [issues](https://github.com/agentscope-ai/agentscope/issues) and [discussions](https://github.com/agentscope-ai/agentscope/discussions), and this is not a duplicate.
- [x] This is a bug, not a usage question. (For questions, please use [Discussions](https://github.com/agentscope-ai/agentscope/discussions/new?category=general) instead.)

### Background / Description

DeveloperOrientedException is intentionally fatal: Toolkit.call_tool re-raises it so programming and framework-contract errors reach the developer instead of being converted into agent-facing tool errors.

The fatal path currently violates the tool-call lifecycle in two steps:

1. Toolkit.call_tool yields the accumulated ToolResponse from finally. When the tool raises DeveloperOrientedException, the generator yields an empty ToolResponse(state=SUCCESS) before the exception continues propagating. A middleware that treats ToolResponse as terminal can stop consuming the generator and hide the fatal error.
2. Once the empty-success emission is removed and the exception reaches the Agent, the reply terminates while its ToolCallBlock can remain unfinished. In Agent Service this leaves the session in awaiting_permission / running state even though the reply has failed.

Concurrent tool execution wraps the fatal exception in ExceptionGroup. The service error classifier checks only the outer exception for DeveloperOrientedException, so the Web UI reports an unknown failure instead of a sanitized internal error.

Expected lifecycle:

```text
DeveloperOrientedException
-> no SUCCESS tool response
-> close every unfinished tool call as FINISHED
-> append a matching ToolResultBlock(INTERRUPTED)
-> terminate the reply with ReplyEnd(ERROR, INTERNAL)
-> keep the raw traceback in server logs with session/reply/tool identifiers
```

This does not change recoverable tool-error behavior. Ordinary exceptions must still become ToolResultState.ERROR results that the agent can observe and reason about.

Related work:

- #1888 / #1905 recover orphan tool calls after mid-stream failures. This report adds the concrete DeveloperOrientedException path and the premature empty-success emission in Toolkit.call_tool.
- #1985 discussed recoverable transport/tool failures. This report preserves the fatal semantics of DeveloperOrientedException.

## Proposed contribution

Because the proposed cleanup changes the core Agent._reply_impl failure path, I would appreciate the maintainers' review of the intended lifecycle and scope before opening a pull request. I already have a working local patch and focused regression tests for the Toolkit response boundary, unfinished tool-call cleanup, and ExceptionGroup classification. If this is considered a valid issue and the approach is acceptable, I would be glad to contribute the fix and adjust it based on your feedback.

### Error Messages

```shell
Before removing the finally emission:

ToolResponse(state=success, content=[])

After allowing the fatal exception to propagate, but before lifecycle cleanup:

ExceptionGroup: One or more tool calls raised an exception
+- DeveloperOrientedException: boom: fatal tool failure

Observed service state after the failed reply:

session status: awaiting_permission
tool call state: asking
Web UI: Reply failed - unknown error
```

### Steps to Reproduce

1. Register a deterministic fatal test tool in Agent Service:

```python
from agentscope.exception import DeveloperOrientedException
from agentscope.tool import FunctionTool, ToolChunk

async def boom_tool() -> ToolChunk:
raise DeveloperOrientedException("boom: fatal tool failure")

async def extra_agent_tools(user_id, agent_id, session_id):
del user_id, agent_id, session_id
return [FunctionTool(boom_tool, description="Fatal error reproduction")]
```

2. Pass `extra_agent_tools=extra_agent_tools` to `create_app` and start the bundled Agent Service and Web UI.
3. In a fresh session, ask the agent to call `boom_tool` exactly once.
4. Approve the tool call when the HITL confirmation card appears.
5. Observe the empty successful result on the original code, or the unfinished tool call / `awaiting_permission` state after only moving the final response out of `finally`.

### Environment

- AgentScope Version: 2.0.8, source checkout at b82253ba1b680a67091c4de32f074477c30ba416
- Python Version: 3.11.15
- OS: macOS 26.6.2 arm64

Contributor guide

Open the contributing guide

Research direction

Start at Toolkit.call_tool and Agent._reply_impl, then inspect the Agent Service error classifier and the focused regression tests mentioned in the report. Trace fatal and ordinary exceptions separately; done means no empty SUCCESS response, unfinished calls close with matching interruption results, ReplyEnd is INTERNAL, and ExceptionGroup classification is sanitized while raw tracebacks remain logged.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, backend, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.