agentscope-ai / agentscope-ai/agentscope-java

[Bug]: AguiResumeCoordinator loses HITL interrupt state across instances

オープン
#2,773 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る
bug
主要言語
Java
スター
5.6k
フォーク
1.3k
平均マージ
4日 12時間
マージ済み PR(30日)
77

説明

**Describe the bug**

The AG-UI interrupt/resume contract state (`pendingInterruptsByThread`, `activeRunsByThread`) lives only in process-local `ConcurrentHashMap`s inside `AguiResumeCoordinator`. When the service is deployed with multiple replicas (e.g. 2 pods on k8s) behind a round-robin load balancer, a resume request can be routed to a replica that never saw the original interrupt. `validate()` then fails with `AGUI_INTERRUPT_CONTRACT_ERROR` (`"RunAgentInput.resume does not match any open interrupt"`), even though the client is correctly following the AG-UI resume contract.

The same state is lost on a single replica whenever the process restarts (rolling update, OOM eviction, failover).

Relevant code:

- `AguiResumeCoordinator.java:43-45` — state held in process-local `ConcurrentHashMap`s
- `AguiResumeCoordinator.java:56-61` — the failing check (`pending == null && hasResume() == true`)
- `AguiRequestProcessor.java:76` — coordinator instantiated inside the processor; no injection or extension point
- `AguiRequestProcessor.java:130-174` — `beginRun` / `trackPendingInterrupts` / `finishRun` wiring

**To Reproduce**

1. Deploy the AG-UI service with 2 replicas behind a round-robin LB (k8s Service / Nginx Ingress):

```yaml
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 2
```

1. Client sends a run request **without** `resume` → the agent issues a `tool_call` → server replies with `RUN_FINISHED` carrying an `interrupt` (`reason: "tool_call"`). The interrupt state is recorded **only on the replica that handled this request** (pod A).
2. Client sends the follow-up run request **with** `resume` (same `threadId`, same `interruptId`) → the LB routes it to the other replica (pod B).
3. Pod B has no record of the interrupt → emits `RUN_ERROR` with `code=AGUI_INTERRUPT_CONTRACT_ERROR`.

With 2 replicas and round-robin routing, roughly 50% of resume attempts fail.

**Expected behavior**

A resume request should be accepted regardless of which replica serves it. Either:

- the interrupt/resume state should be shared across replicas (e.g. a pluggable state store with a Redis backend, consistent with the existing `RedisDistributedStore` approach), or
- the limitation should be explicitly documented (e.g. "sticky sessions required for AG-UI tool-call interrupts").

**Error messages**

```json
{"type":"RUN_ERROR","threadId":"1787102227357","runId":"run-1787102359437","message":"RunAgentInput.resume does not match any open interrupt","code":"AGUI_INTERRUPT_CONTRACT_ERROR","timestamp":1787102359738}
```

**Environment (please complete the following information):**

- AgentScope-Java Version: 2.0.1
- Java Version: 17
- OS: Linux (k8s pods); reproduced from a Windows dev client

**Additional context**

- The official multi-replica guidance (`docs/v2/en/docs/others/going-to-production.md`) recommends `RedisDistributedStore` + `OssFilesystemSpec` and states "no sticky sessions required", but that applies to session/workspace state. The AG-UI resume coordinator has **no distributed backend and no SPI** to plug one in — `AguiResumeCoordinator` is a package-private `final` class with hardcoded `ConcurrentHashMap`s.
- `validate()` also requires the resume `interruptId` set to match the pending set **exactly** (`AguiResumeCoordinator.java:84-95`, missing/unknown ids are reported), so any mismatch fails hard — there is no lenient path.
- There is no TTL/cleanup for pending interrupt entries (`trackPendingInterrupts`, `AguiResumeCoordinator.java:180-198`); entries stay in memory until the next run of the same thread.

Suggested direction:

1. Extract the state access behind a small SPI (e.g. `AguiResumeStateStore`) with the in-memory implementation as the default, and provide a Redis-backed implementation in `agentscope-extensions-redis`.
2. Preserve cross-replica atomicity: `putIfAbsent`-style claim for `beginRun` (SET NX) and conditional delete for `finishRun` (compare-and-delete).
3. Either way, document the multi-replica requirement for AG-UI interr

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。