Skip to content

Conversation

@abhipatel12
Copy link
Contributor

Summary

Migrate packages/a2a-server to use the modern, event-driven Scheduler by default. This change replaces the legacy direct callback mechanism with a MessageBus listener in the Task class, aligning A2A with the architectural direction of Gemini CLI.

Details

  • A2A Migration: Transitioned packages/a2a-server to use the Scheduler instead of CoreToolScheduler.
  • Event-Driven Flow: Implemented a bus-based mechanism for tool status updates, live output, and confirmations using correlationId.
  • Dual Support: Added an experimental flag enableEventDrivenScheduler (defaulting to true for A2A) to provide an escape hatch while ensuring immediate migration.
  • Core Export: Exported the Scheduler class from packages/core to enable its consumption in A2A.
  • Improved Design: Removed the need for direct function callbacks in tool confirmations, using serializable TOOL_CONFIRMATION_RESPONSE events instead.

Related Issues

Related to moving towards a default true state for enableEventDrivenScheduler.

How to Validate

  1. Run A2A server tests: npm test -w @google/gemini-cli-a2a-server -- src/agent/task.test.ts src/agent/task-event-driven.test.ts
  2. Run full preflight: npm run preflight
  3. Verify that A2A functions correctly with the event-driven scheduler (default behavior).

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

This change transitions packages/a2a-server to use the event-driven
Scheduler by default. It replaces the legacy direct callback mechanism
with a MessageBus listener in the Task class to handle tool status
updates, live output, and confirmations.

- Added experimental.enableEventDrivenScheduler setting (defaults to true).
- Refactored Task.ts to support both legacy and event-driven schedulers.
- Implemented bus-based tool confirmation responses using correlationId.
- Exported Scheduler from packages/core.
- Added unit tests for the event-driven flow in A2A.
@abhipatel12 abhipatel12 requested a review from a team as a code owner January 20, 2026 20:55
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @abhipatel12, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the tool scheduling mechanism within the a2a-server to adopt an event-driven paradigm. By transitioning from direct callbacks to a MessageBus-based system, the change aims to enhance the modularity, scalability, and responsiveness of tool interactions. This aligns the A2A server with a more modern architectural approach, improving how tool states, outputs, and user confirmations are managed and communicated across the system.

Highlights

  • Event-Driven Scheduler Migration: The a2a-server package has been migrated to use a modern, event-driven Scheduler by default, replacing the previous callback-based CoreToolScheduler.
  • MessageBus Integration: Tool status updates, live output, and confirmations now leverage a MessageBus listener within the Task class, using correlationId for an event-driven flow.
  • Dual Scheduler Support: An experimental flag, enableEventDrivenScheduler, has been introduced (defaulting to true for A2A) to allow for a graceful transition and provide an escape hatch if needed.
  • Improved Tool Confirmation: The design for tool confirmations has been improved by removing direct function callbacks and instead using serializable TOOL_CONFIRMATION_RESPONSE events.
  • Core Scheduler Export: The Scheduler class is now exported from the packages/core module, making it available for consumption by other parts of the system, such as A2A.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant architectural improvement by migrating the A2A server to an event-driven tool scheduler. The changes are well-structured, and the new logic for handling tool calls via the message bus is clearly implemented. The addition of the enableEventDrivenScheduler flag provides a safe way to manage this transition. I've found one area with redundant code that can be simplified for better maintainability.

Comment on lines +789 to +793
if (this.scheduler instanceof Scheduler) {
await this.scheduler.schedule(updatedRequests, abortSignal);
} else {
await this.scheduler.schedule(updatedRequests, abortSignal);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This if/else block contains identical code in both branches. Since the schedule method exists on both Scheduler and CoreToolScheduler with compatible signatures for this call site (the return value is not used), this conditional logic is redundant and can be simplified to a single line. This will improve code clarity and maintainability.

    await this.scheduler.schedule(updatedRequests, abortSignal);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential bug?

@github-actions
Copy link

Size Change: +31.4 kB (+0.14%)

Total Size: 23.2 MB

Filename Size Change
./bundle/gemini.js 23.2 MB +31.4 kB (+0.14%)
ℹ️ View Unchanged
Filename Size
./bundle/sandbox-macos-permissive-closed.sb 1.03 kB
./bundle/sandbox-macos-permissive-open.sb 890 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB
./bundle/sandbox-macos-restrictive-closed.sb 3.29 kB
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB

compressed-size-action

@gemini-cli gemini-cli bot added the status/need-issue Pull requests that need to have an associated issue. label Jan 20, 2026
return scheduler;
}

private _setupEventDrivenScheduler(): Scheduler {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the history behind the '_' prefix? Both of these are private instance functions. It seems like it's inconsistently used.

messageBus.subscribe(
MessageBusType.TOOL_CALLS_UPDATE,
(message: unknown) => {
const event = message as ToolCallsUpdateMessage;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this cast is only necessary because you aren't passing the right type parameter to subscribe. I think you can instead do:

messageBus.subscribe<ToolCallsUpdateMessage>(
   MessageBusType.TOOL_CALLS_UPDATE,
    (message: ToolCallsUpdateMessage) => {
      if (event.type !== MessageBusType.TOOL_CALLS_UPDATE) {
        return;
      }
      ...
    }
)

// Inject a dummy onConfirm for legacy UI compatibility if needed,
// though A2A should use the correlationId-based path now.
onConfirm: async () => {},
} as ToolCallConfirmationDetails);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unneeded cast, please delete.

In general you should rarely need to cast in TS if you have the proper typings available.

Despite this Gemini CLI seems to add them all over. I'd recommend doing a quick Cmd+F for as before checking in each change until we fix it.


messageBus.subscribe(
MessageBusType.TOOL_CALLS_UPDATE,
(message: unknown) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider extracting the bulk of this out when you have lambdas spanning more than a dozen or so lines.


const toolCalls = event.toolCalls;

toolCalls.forEach((tc) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this lambda seems like another great candidate for a named function.

if (tc.status === 'awaiting_approval' && tc.confirmationDetails) {
// Bridge the new serializable details back to the legacy shape for A2A UI
const details =
tc.confirmationDetails as SerializableConfirmationDetails;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like another unnecessary cast.

this.eventBus?.publish(statusUpdate);
}

// 5. Handle Auto-Execution (YOLO)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: another thing that might help with readability/maintainability here is if each of these numbered sections was its own function.

this.config.getApprovalMode() === ApprovalMode.YOLO)
) {
logger.info(`[Task] Auto-approving tool call ${callId}`);
void messageBus.publish({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not awaiting this promise. Any risk of race condition in letting this code interleave?

if (confirmationDetails.type === 'edit') {
const payload = part.data['newContent']
? ({
newContent: part.data['newContent'] as string,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be using typeof === 'string' instead so this doesn't cause a runtime failure if it's something else?

const payload = part.data['newContent']
? ({
newContent: part.data['newContent'] as string,
} as ToolConfirmationPayload)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast is unneeded

Copy link
Member

@gundermanc gundermanc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some suggestions.

@gemini-cli
Copy link
Contributor

gemini-cli bot commented Jan 27, 2026

Hi there! Thank you for your contribution to Gemini CLI.

To improve our contribution process and better track changes, we now require all pull requests to be associated with an existing issue, as announced in our recent discussion and as detailed in our CONTRIBUTING.md.

This pull request is being closed because it is not currently linked to an issue. Once you have updated the description of this PR to link an issue (e.g., by adding Fixes #123 or Related to #123), it will be automatically reopened.

How to link an issue:
Add a keyword followed by the issue number (e.g., Fixes #123) in the description of your pull request. For more details on supported keywords and how linking works, please refer to the GitHub Documentation on linking pull requests to issues.

Thank you for your understanding and for being a part of our community!

@gemini-cli gemini-cli bot closed this Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status/need-issue Pull requests that need to have an associated issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants