Please make sure you read the contribution guide and file the issues in the right place.
Contribution guide.
🔴 Required Information
Is your feature request related to a specific problem?
When a tool fails, adk-java gives the model no way to learn what went wrong and try again. A tool
that reports its failure ends the invocation on the first attempt, even when the failure is one the
model could have corrected — malformed arguments, the wrong tool for the job, a precondition it
could have satisfied first.
adk-python ships a plugin for exactly this — ReflectAndRetryToolPlugin. adk-java
has no equivalent.
Describe the Solution You'd Like
ReflectAndRetryToolPlugin in com.google.adk.plugins.reflectandretry, registered on a Runner
like any other plugin, behaving as adk-python's does:
- intercept tool failures through the two hooks that already exist on
Plugin —
onToolErrorCallback for a tool that signals an error, and afterToolCallback for a result that
carries one;
- track consecutive failures per tool within a scope, so a success with one tool resets that
tool's counter without forgiving another's;
- substitute a structured reflection response — error type, error details, retry count, and guidance
telling the model to analyze the arguments and not repeat the identical call — for each failure up
to maxRetries;
- past the limit, either propagate the original error or return a final "stop using this tool"
message, per a constructor flag;
- scope the counters per invocation (default) or globally, via a
TrackingScope enum.
No existing class changes and no existing API changes: registration uses the plugin surfaces already
present.
Impact on your work
Applications wanting adk-python's behavior have to reimplement it in application code, where it
drifts from upstream. Not blocking and there is no timeline — this is a parity gap, not an outage.
Willingness to contribute
Yes. A PR follows immediately after this issue: one new public plugin class plus four small
supporting types in a plugins.reflectandretry subpackage, with tests. No existing file is
modified.
🟡 Recommended Information
Describe Alternatives You've Considered
Retry inside the tool itself. This is the obvious workaround and it is not the same thing. A
loop inside the tool repeats the identical call; the point of reflection is to hand the model the
error and its own arguments so it can call differently. A tool cannot fix arguments it did not
choose.
Implement the plugin in application code. It works — the behavior above was verified using
public API only. But it is per-application boilerplate for something the plugin surface exists to
ship once, and every copy drifts from upstream separately.
Widen onToolErrorCallback semantics instead. That would change existing behavior for every
plugin. A new opt-in plugin changes nothing for anyone who does not register it.
Proposed API / Implementation
Registration needs no new API:
Runner runner =
new InMemoryRunner(agent, "my-app", ImmutableList.of(new ReflectAndRetryToolPlugin(3)));
Both hooks are already declared on Plugin, and both already return the type the plugin needs —
Maybe<Map<String, Object>>, matching Python's Optional[dict[str, Any]]:
// Plugin.java:188
default Maybe<Map<String, Object>> afterToolCallback(
BaseTool tool, Map<String, Object> toolArgs, ToolContext toolContext, Map<String, Object> result)
// Plugin.java:206
default Maybe<Map<String, Object>> onToolErrorCallback(
BaseTool tool, Map<String, Object> toolArgs, ToolContext toolContext, Throwable error)
The class is written and tested; the PR carries it.
Additional Context
Observed on 1.7.1-SNAPSHOT, Windows 11 (not OS-specific).
Please make sure you read the contribution guide and file the issues in the right place.
Contribution guide.
🔴 Required Information
Is your feature request related to a specific problem?
When a tool fails, adk-java gives the model no way to learn what went wrong and try again. A tool
that reports its failure ends the invocation on the first attempt, even when the failure is one the
model could have corrected — malformed arguments, the wrong tool for the job, a precondition it
could have satisfied first.
adk-pythonships a plugin for exactly this —ReflectAndRetryToolPlugin.adk-javahas no equivalent.
Describe the Solution You'd Like
ReflectAndRetryToolPluginincom.google.adk.plugins.reflectandretry, registered on aRunnerlike any other plugin, behaving as adk-python's does:
Plugin—onToolErrorCallbackfor a tool that signals an error, andafterToolCallbackfor a result thatcarries one;
tool's counter without forgiving another's;
telling the model to analyze the arguments and not repeat the identical call — for each failure up
to
maxRetries;message, per a constructor flag;
TrackingScopeenum.No existing class changes and no existing API changes: registration uses the plugin surfaces already
present.
Impact on your work
Applications wanting adk-python's behavior have to reimplement it in application code, where it
drifts from upstream. Not blocking and there is no timeline — this is a parity gap, not an outage.
Willingness to contribute
Yes. A PR follows immediately after this issue: one new public plugin class plus four small
supporting types in a
plugins.reflectandretrysubpackage, with tests. No existing file ismodified.
🟡 Recommended Information
Describe Alternatives You've Considered
Retry inside the tool itself. This is the obvious workaround and it is not the same thing. A
loop inside the tool repeats the identical call; the point of reflection is to hand the model the
error and its own arguments so it can call differently. A tool cannot fix arguments it did not
choose.
Implement the plugin in application code. It works — the behavior above was verified using
public API only. But it is per-application boilerplate for something the plugin surface exists to
ship once, and every copy drifts from upstream separately.
Widen
onToolErrorCallbacksemantics instead. That would change existing behavior for everyplugin. A new opt-in plugin changes nothing for anyone who does not register it.
Proposed API / Implementation
Registration needs no new API:
Both hooks are already declared on
Plugin, and both already return the type the plugin needs —Maybe<Map<String, Object>>, matching Python'sOptional[dict[str, Any]]:The class is written and tested; the PR carries it.
Additional Context
Observed on
1.7.1-SNAPSHOT, Windows 11 (not OS-specific).