google / google/adk-go

Plugin callbacks not propagated to sub-agent runners in agenttool

Open
#669 1 comment 2 reactions 0 assignees View on GitHub
bug
Dominant language
Go
Stars
8.8k
Forks
1k
Avg merge
3d 18h
Merged PRs (30d)
88

Description

## Bug

When a `Plugin` is registered on a `Runner` via `PluginConfig`, its callbacks (`BeforeToolCallback`, `AfterToolCallback`, etc.) do not fire for tools executed inside a sub-agent invoked via `agenttool`.

## Root Cause

`agenttool.Run()` creates a new `runner.Runner` at [agent_tool.go:170-177](https://github.com/google/adk-go/blob/main/tool/agenttool/agent_tool.go#L170-L177) **without forwarding `PluginConfig`**:

```go
r, err := runner.New(runner.Config{
AppName: t.agent.Name(),
Agent: t.agent,
SessionService: sessionService,
ArtifactService: artifact.InMemoryService(),
MemoryService: memory.InMemoryService(),
// PluginConfig not set — sub-runner has no plugins
})
```

The parent runner stores its plugin manager in context at [runner.go:160](https://github.com/google/adk-go/blob/main/runner/runner.go#L160):
```go
ctx = plugininternal.ToContext(ctx, r.pluginManager)
```

The sub-runner's `Run()` then **overwrites** the parent's plugin manager with its own empty one at the same line. The parent's plugins are lost.

## Impact

- Observability plugins (logging, tracing, metrics) are blind to tool calls inside sub-agents
- `BeforeToolCallback` / `AfterToolCallback` only fire for the `agenttool` wrapper itself, not for function tools inside the sub-agent
- Same issue affects `RunLive()` (line 337)

## Reproduction

1. Create a parent agent with a sub-agent wrapped via `agenttool.New()`
2. Sub-agent has a function tool (e.g., `functiontool.New(...)`)
3. Register a plugin with `BeforeToolCallback` on the parent runner
4. Run the parent agent with input that triggers delegation to the sub-agent
5. Observe: plugin callback fires for `tool=sub_agent_name` but NOT for the inner function tool

## Suggested Fix

Add a `HasPlugins()` method to `PluginManager` and make `runner.Run()` / `runner.RunLive()` conditional:

```go
// runner.go:160 (and :337 for RunLive)
// Only overwrite if this runner has its own plugins — otherwise inherit parent's
if r.pluginManager.HasPlugins() {
ctx = plugininternal.ToContext(ctx, r.pluginManager)
}
```

This preserves backwards compatibility: sub-runners with explicit plugins override; sub-runners without plugins (agenttool case) inherit the parent's.

## Related

- Same issue reported in adk-python: google/adk-python#2809, google/adk-python#1746
- The `TODO` comments in `agent_tool.go` (lines 174, 200) suggest forwarding services is planned but not yet implemented

Contributor guide

Open the contributing guide

Research direction

Start in tool/agenttool/agent_tool.go around lines 170-177 and 337, then read runner/runner.go at the plugin-context handling points around lines 160 and 337. Trace how the parent and sub-runner plugin managers enter context, and verify both Run and RunLive preserve inherited callbacks while explicit sub-runner plugins still override. Done means inner sub-agent tool calls trigger the parent callbacks without breaking existing plugin behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
ai, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.