Repository navigation
Description
Required Information
Is your feature request related to a specific problem?
Every turn of an ADK Go agent resends the full system instruction, every tool declaration and the whole conversation history to Gemini. For agents with long instructions, large toolsets (MCP, skills, OpenAPI) or long sessions, most of each request is a prefix the model has already processed. That prefix is paid for in full input tokens and latency on every call.
Gemini supports explicit context caching (CachedContent), and ADK exposes it as ContextCacheConfig in adk-python (v1.15.0), adk-java (v0.1.0) and adk-kotlin (v0.7.0) (docs). ADK Go has no equivalent: CachedContent is only read back today as CachedContentTokenCount for telemetry and agent analytics, and nothing in the flow creates or reuses a cache.
Proposed Solution
Port the existing cross-ADK design rather than invent a Go dialect. The behavior follows adk-python's GeminiContextCacheManager and ContextCacheRequestProcessor:
- Opt-in config on the runner. A
ContextCachefield onrunner.Config, next toCompaction. Nil (the default) disables caching, so existing callers are unaffected. Fields and defaults match the other ADKs:CacheIntervals(default 10, range 1–100): maximum invocations that reuse one cache before it is refreshed.TTL(default 30m): lifetime of a created cache.MinTokens(default 0): minimum prompt token count of the previous request before a cache is created.CreateHTTPOptions(optional): for example a timeout onCaches.Create. When creation fails or times out, the request proceeds uncached.
- Request processor in
internal/llminternal. This runs before contents are sent. It walks the session's events newest first for the current agent. It finds the latest cache metadata, incrementingInvocationsUsedwhen that metadata comes from an earlier invocation, and the previous prompt token count. Both go onto the request. - Gemini cache manager in
model/gemini. It uses the client's existingCachesAPI (Create/Delete):- It fingerprints the cacheable prefix: model, backend scope, system instruction, canonicalized tools, tool config and the first N contents. N is everything before the trailing run of user contents.
- It reuses a cache while it has not expired,
InvocationsUsed <= CacheIntervals, and the fingerprint still matches. - On invalidation it deletes the old cache. If the prefix is unchanged it creates a new one. Otherwise it returns fingerprint-only metadata so the next turn can try again.
- It never creates a cache on a session's first request, below
MinTokens, or below the model's minimum cache size (2048 tokens for Gemini 2.5, 4096 for Gemini 3). - It applies a cache by setting
Config.CachedContentand dropping the system instruction, tools, tool config and cached contents from the request. The final content is always kept.
CacheMetadataonmodel.LLMResponse. Becausesession.EventembedsLLMResponse, the metadata travels with the event, which is how the next turn finds it. The type has two states, active (CacheName,ExpireTime,InvocationsUsed,CreatedAt) and fingerprint-only (Fingerprint,ContentsCount), plus anExpireSoon()helper. Token counts stay inUsageMetadata, as in the other ADKs.
Models other than Gemini ignore the config, so openaimodel is unchanged. This is independent of #1866, which adds Anthropic's own cache_control breakpoints inside the Anthropic model package. That PR does not manage Gemini CachedContent resources.
Impact on your work
This brings ADK Go to parity with three other ADKs on a feature that directly cuts cost and latency for production agents. It is also a prerequisite for a later StaticInstruction on llmagent, the stable-prefix instruction adk-python pairs with caching.
Recommended Information
Alternatives Considered
- Setting
GenerateContentConfig.CachedContentby hand. This works for one static cache. But the caller then has to create, refresh and delete caches themselves, track expiry and invalidate when tools or instructions change. They also have to strip the cached fields from each request, because Gemini rejects a request that sends them alongsideCachedContent. That is the lifecycle this feature owns. - A plugin. Plugins see the request but cannot persist per-agent cache state on events or reach the model's client cleanly. The other ADKs put this in the flow and the model layer for that reason.
Willingness to contribute
Yes — I'd like to implement this, and I'm starting on it now. The core logic (fingerprinting, cache validation and invalidation, the request processor, and httprr-recorded tests) doesn't depend on the open questions below, so I'll have a PR ready to open shortly after we agree on them.
Following the guidance on #245, the PR will stay focused and reviewable on its own: config, metadata, the request processor and the Gemini cache manager, with unit tests, recorded tests and an example. StaticInstruction would be a separate follow-up. If you'd rather see it split further (for example, types and the processor first, then the Gemini manager), I'm happy to do that.
Proposed API / Implementation
r, err := runner.New(runner.Config{
AppName: "app",
Agent: rootAgent,
SessionService: sessionService,
ContextCache: &contextcache.Config{
CacheIntervals: 10,
TTL: 30 * time.Minute,
MinTokens: 4096,
},
})// On model.LLMResponse, and therefore on session.Event.
type CacheMetadata struct {
CacheName string // empty in the fingerprint-only state
ExpireTime time.Time
InvocationsUsed int
CreatedAt time.Time
Fingerprint string
ContentsCount int
}Questions for maintainers:
- Package placement. Should the config and metadata types go in a stable package (for example
modelor a newcontextcachepackage)? Or should the feature start underexp/, given that adk-python still marks the manager@experimental? Therunner.ConfigandLLMResponsefields have to be stable API either way. - Persistence.
session/databasestores response metadata in per-field columns, soCacheMetadataneeds its own column or has to ride along in an existing JSON column. Which do you prefer? Session services that drop the field degrade safely: every turn is fingerprint-only and nothing is cached. - Workflows and resumption. The processor scopes metadata by agent name, as in adk-python. Does that hold for the same agent placed in several workflow nodes, or should the key include the branch?
Additional Context
- adk-python reference:
agents/context_cache_config.py,models/cache_metadata.py,models/gemini_context_cache_manager.py,flows/llm_flows/context/_cache.py. - Gemini docs: https://ai.google.dev/gemini-api/docs/caching
Activity
indurireddy-TF commentedon Oct 23, 2026
Hi @kdroste-google , tagging you to review this feature request when you get a chance. It outlines an enhancement for Gemini context caching (ContextCacheConfig) to align Go with the Python and Java/Kotlin SDKs.
karthik-0306 commentedon Oct 23, 2026
@kdroste-google I've opened the implementation in #1951 . Since the design questions on this issue are still open, I've answered each one in the PR so you have something concrete to approve or change:
model(model.ContextCacheConfig,model.CacheMetadata), becauseLLMRequestandLLMResponsereference them and every model implementation needs them. A separate package would only be imported back intomodel. The PR is all stable API, with nothing underexp/. The hooks (runner.Config.ContextCache,LLMResponse.CacheMetadata) have to be stable either way, and the design is a direct port of what already ships in Python, Java and Kotlin.session/databasegets a nullablecache_metadatacolumn, following what fix(session/database): persist input and output transcriptions #1662 did for the transcription columns. Vertex AI carries the field inraw_event. This does mean database users who manage their own schema must add the column before upgrading, even with caching off. I've put that first in the PR description and would like your call on whether it needs thebreaking-changelabel.A few things go beyond a straight port of Python. They're listed under "Differences from adk-python", each with its own tests, and any of them can be dropped if you'd rather keep the first PR closer to the reference.
One limit on testing: I could only run it against a free-tier Gemini key, which refuses cache creation, so the successful-create path is covered by tests but not yet confirmed live. If someone with a billing-enabled key can run
go run ./examples/contextcache/, that would close the gap.