Skip to content

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

Description

@rshashank17

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

<clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn js-clipboard-copy m-2 p-0" data-copy-feedback="Copied!" data-tooltip-direction="w" value="// 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" tabindex="0" role="button" style="box-sizing: border-box; margin: 8px !important; padding: 0px !important; font-size: 14px; font-weight: 500; white-space: nowrap; vertical-align: middle; cursor: pointer; user-select: none; appearance: none; border: 1px solid rgb(61, 68, 77); border-radius: 6px; line-height: 20px; display: inline-block; position: relative; color: rgb(240, 246, 252); background-color: rgb(33, 40, 48); box-shadow: none; transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1), background-color 80ms cubic-bezier(0.33, 1, 0.68, 1), box-shadow 80ms cubic-bezier(0.33, 1, 0.68, 1), border-color 80ms cubic-bezier(0.33, 1, 0.68, 1);">

Internal Checkpoints (minimum)

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

  1. Before each LLM call  BaseLlmFlow.runOneStep() (already checks endInvocation, easy to extend)
  2. Before each tool execution  Functions.handleFunctionCalls() (currently has NO cancellation check)
  3. During LLM streaming — between chunks (for long generations)
  4. Plugin/callback boundaries  beforeModel / 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).

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 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:

Before each LLM call — BaseLlmFlow.runOneStep() (already checks endInvocation, easy to extend)
Before each tool execution — Functions.handleFunctionCalls() (currently has NO cancellation check)
During LLM streaming — between chunks (for long generations)
Plugin/callback boundaries — beforeModel / 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
API preference: CancellationToken (explicit, testable) vs. leveraging RxJava Disposable semantics (less new API surface but less portable)?
Scope: Should cancellation also cover runLive (streaming/live mode), or start with runAsync only?
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
TypeScript ADK cancel docs: https://google.github.io/adk-docs/runtime/cancel/
Python ADK cancellation PR: google/adk-python#4825
Python ADK /cancel API PR: google/adk-python#5983
Python ADK cancellation discussion: google/adk-python#4156
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).

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationquestionFurther information is requested

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions