Skip to content

Feature Request: Run Cancellation / Abort Support for Java ADK #1341

Description

@zhangxiangnan

Summary

Java ADK currently lacks a mechanism to cancel/abort a running agent invocation. TypeScript ADK already ships mature AbortSignal-based cancellation (docs/runtime/cancel.md), and Python ADK has active work in progress (google/adk-python#4825, google/adk-python#5983). Java ADK has no equivalent — no API, no internal checkpoints, and no graceful shutdown path.

Motivation

Cancellation is essential for production agent deployments:

  • User-facing "Stop" button — long-running multi-tool loops need immediate, graceful termination when the user clicks stop.
  • Timeout enforcement — server-side request deadlines (e.g., HTTP gateway timeouts) must propagate into the agent execution.
  • Resource cleanup — without cooperative cancellation, in-flight LLM streaming connections and tool executions leak resources.
  • Cost control — aborting early avoids unnecessary LLM token consumption.

Current State

Language Status Mechanism
TypeScript ✅ Shipped runner.runAsync(params, { signal }) with AbortSignal; checkpoints in LLM call, tool execution, and plugin callbacks; graceful generator completion (no throw)
Python 🚧 In progress google/adk-python#4825 (stop_event: asyncio.Event), google/adk-python#5983 (/cancel API + task.cancel())
Java ❌ Missing Only InvocationContext.endInvocation (internal flag, not externally triggerable); Disposable.dispose() is a hard RxJava cut (not graceful, tools don't check it); no RunConfig timeout/cancel field

Proposed Design (aligned with TypeScript)

API Surface

// Option A: CancellationToken (similar to .NET / structured concurrency)
CancellationTokenSource cts = new CancellationTokenSource();
RunConfig config = RunConfig.builder()
    .cancellationToken(cts.token())
    .build();

Flowable<Event> events = runner.runAsync(userId, sessionId, content, config);
// Later:
cts.cancel(); // triggers graceful shutdown

// Option B: RxJava-native (Disposable already exists, but needs cooperative checks)
// Enhance internal checkpoints to check subscription disposal state

Internal Checkpoints (minimum)

Following TypeScript's lead, cancellation should be checked at:

  1. Before each LLM callBaseLlmFlow.runOneStep() (already checks endInvocation, easy to extend)
  2. Before each tool executionFunctions.handleFunctionCalls() (currently has NO cancellation check)
  3. During LLM streaming — between chunks (for long generations)
  4. Plugin/callback boundariesbeforeModel / afterModel / beforeTool / afterTool

Semantics (align with TypeScript cancel.md)

  • Graceful: the Flowable completes normally (no error signal), already-emitted events are preserved in the session.
  • Cooperative: cancellation is checked at defined points; in-flight atomic operations (single LLM call, single tool run) may complete before the next checkpoint.
  • Idempotent: calling cancel multiple times is safe.

Questions for Maintainers

  1. API preference: CancellationToken (explicit, testable) vs. leveraging RxJava Disposable semantics (less new API surface but less portable)?
  2. Scope: Should cancellation also cover runLive (streaming/live mode), or start with runAsync only?
  3. Event emission on cancel: Should the framework emit a final "cancelled" event (like Python's temp:cancelled state proposal), or just complete silently (like TypeScript)?

References


I'm happy to contribute the implementation once the API direction is agreed upon. I have production experience implementing cooperative cancellation in a Java ADK-based agent platform (checkpoint-based, with checks at beforeModel/beforeTool/afterEvent boundaries).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions