Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Cache-stable progressive disclosure of tools (deferred tools + per-turn selection hook)

Aperta
#7,241 1 commento 0 reazioni 1 assegnatario Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Funzionalità
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
python
Ambito
ai, tooling

Direzione di ricerca

Read tools/skill_toolset.py and models/gemini_context_cache_manager.py first, then trace tool resolution through functions.py and the flow code. Check how callbacks, plugins, argument validation, events, and cache history are handled today. Done means a design or implementation that keeps config.tools stable, persists deduplicated declarations, resolves deferred calls under real names, validates arguments, and supports per-turn selection with search_tools fallback.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

needs review tools

Is your feature request related to a specific problem?

Yes. #4899 asked for tool search / progressive disclosure and was closed in favour of SkillToolset(additional_tools=...) together with adk_additional_tools in SKILL.md frontmatter. We use that mechanism heavily in production, across a dozen or so skills in several agents. It works for scoping tools, but three properties make it a poor fit for large toolsets on long, cached sessions.

Activating a skill changes config.tools, so the context cache misses.

SkillToolset.get_tools() adds the activated skills' tools to the request (_resolve_additional_tools_from_state, tools/skill_toolset.py, ADK 2.9.0).
GeminiContextCacheManager._generate_cache_fingerprint hashes config.tools along with the system instruction and the first N contents (models/gemini_context_cache_manager.py).
So every skill activation produces a new fingerprint. The session's explicit cache stops matching and has to be rebuilt partway through the conversation. Progressive disclosure and prefix caching are working against each other.
Activation only ever grows. The adk_activated_skill state list is append-only. After a few turns the agent carries the union of every tool it has touched, which is the "all tools up front" problem again, reached more slowly. Nothing lets you scope tools to a turn, or deactivate them.

Only the LLM can do the selecting. A tool becomes visible only when the model calls load_skill. There is no supported hook for choosing relevant tools per turn with something cheaper or more accurate, such as:

an embedding retriever;
a small classifier;
a structured-decision model;
deterministic rules.
The model also has to pick a skill bundle, not individual tools, so tool granularity is tied to how the skills are authored.

Describe the solution you'd like

First-class deferred tools: tools that are registered with the agent but never appear in config.tools. Their declarations are delivered through contents, and ADK executes them as if they were declared. Rough shape:

root_agent = LlmAgent(
name="concierge",
tools=[core_toolset], # always declared, cache-stable
deferred_tools=DeferredTools(
tools=[big_toolset_a, big_toolset_b], # registered, not declared
selector=my_selector, # optional per-turn hook (below)
search=True, # expose a built-in search_tools fallback
),
)
Requirements:

The declared tool surface stays fixed. config.tools holds only the always-on tools plus a fixed pair of built-ins:

search_tools(query) -> [declarations]
call_tool(name, args), or an equivalent resolution mechanism
The cache fingerprint therefore never changes because a tool was disclosed.

Disclosed declarations are persisted as session events, not request-only contents. This could be a function response from search_tools, or an event ADK writes when the selector runs. If a before_model hook injected them only into the outgoing request, they would sit inside the fingerprinted prefix but be missing from the stored history, so the cache would miss at every turn boundary. ADK should also deduplicate: a declaration already in history isn't re-sent.

Deferred tools run through the full tool pipeline under their real name. call_tool("get_order", {...}) must:

resolve to the real BaseTool;
run before_tool_callback / after_tool_callback and plugin tool callbacks with tool.name == "get_order";
emit events, traces and telemetry under the real name.
Guardrail plugins, analytics and auth wrappers that key on tool names must work unchanged. If they all see call_tool instead, the feature is unusable wherever there are guards.

Arguments are validated against the real schema. Deferred calls lose the model's constrained decoding, so ADK should validate args against the tool's declaration. On failure it should return a structured, model-readable error, not raise. The existing FunctionTool argument parsing is a natural place for this.

A per-turn selector hook. Signature something like:

async def selector(ctx: ReadonlyContext, candidates: list[ToolSummary]) -> list[str]
It runs once per invocation, before the first model call. It returns the tool names to disclose this turn, and ADK persists their declarations per requirement 2. The hook lets applications plug in a retriever, a classifier or rules. Without a selector, the model uses search_tools itself.

Tools the selector missed stay reachable. search_tools is always available as the fallback. This turns a missed selection into one extra round-trip instead of an unreachable tool.

Optional scoping. A per-turn or TTL option for disclosed tools, so their declarations and activation state don't pile up over a long session.

Describe alternatives you've considered

SkillToolset + adk_additional_tools: covered above. The cache misses on activation, activation never shrinks, and only the LLM can select.
BaseToolset.get_tools(readonly_context) / tool_filter returning a per-turn subset: this supports per-turn scoping. But the subset is config.tools, so the cache fingerprint changes every time the subset does. That's the same problem as the first alternative, only worse, because it can change every turn.
An application-level call_tool dispatcher plus schemas injected into contents: this is the design we'd have to build ourselves. It bypasses the per-tool callback and plugin chain, and it needs every name-keyed plugin rewritten to unwrap the dispatcher. That belongs in the framework.
A per-turn hint only ("likely relevant: X"), keeping every tool declared: cache-stable and cheap, but it doesn't reduce declaration tokens or selection distractors for large toolsets.
Additional context

Anthropic's API has tool search with deferred loading (defer_loading), linked from #4899. The linked Microsoft Agent Framework issue covers the same idea.
The request here is the ADK equivalent, with two additions: it must keep the Gemini context cache stable, and it must keep ADK's tool callbacks and plugins intact.
We're happy to contribute a design doc or PR. We'd especially like guidance on where name resolution should hook into functions.py / the flow, so callbacks see the real tool.

Lingua principale
Python
Stelle
21.6k
Fork
4k
Merge medio
13h 49m
PR unite (30g)
10

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di google/adk-python

Tutte le issue di google/adk-python

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.