Plugin callbacks not propagated to sub-agent runners in agenttool
- 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
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