Skip to content

feat(go-adk): add compaction support to Go ADK runtime#1971

Open
yashrajshuklaaa wants to merge 3 commits into
kagent-dev:mainfrom
yashrajshuklaaa:feat/go-adk-compaction
Open

feat(go-adk): add compaction support to Go ADK runtime#1971
yashrajshuklaaa wants to merge 3 commits into
kagent-dev:mainfrom
yashrajshuklaaa:feat/go-adk-compaction

Conversation

@yashrajshuklaaa

Copy link
Copy Markdown

Implements post-invocation event history compaction for the Go ADK runtime
The upstream Go ADK v1.1.0 does not ship this so it is implemented within kagent via a new compaction package
Closes #1783

Copilot AI review requested due to automatic review settings June 6, 2026 13:07
@github-actions github-actions Bot added the enhancement New feature or request label Jun 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds post-invocation “context compaction” support to the Go runtime by introducing a compaction package, wiring it into the A2A executor, and enabling in-memory session event replacement.

Changes:

  • Added go/adk/pkg/compaction with configuration parsing and compaction/summarization logic.
  • Extended runner creation to return a compaction config and wired compaction into KAgentExecutor.Execute.
  • Added ReplaceEvents to local sessions to allow atomic in-memory event history rewrites after persisting a summary.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
go/core/internal/controller/reconciler/reconciler.go Updates runtime feature notes to reflect compaction support.
go/adk/pkg/session/local_session.go Adds an atomic ReplaceEvents method used by compaction.
go/adk/pkg/runner/adapter.go Returns compaction config from CreateRunnerConfig via compaction.FromAgentConfig.
go/adk/pkg/compaction/compaction.go Introduces compaction implementation and AgentConfig-to-Config translation.
go/adk/pkg/compaction/compaction_test.go Adds initial unit tests for compaction helpers and nil-compactor behavior.
go/adk/pkg/a2a/executor.go Instantiates a compactor and runs post-invocation compaction.
go/adk/cmd/main.go Plumbs compaction config into executor and logs when enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread go/adk/pkg/session/local_session.go
Comment thread go/adk/pkg/compaction/compaction.go
Comment thread go/adk/pkg/compaction/compaction.go
Comment thread go/adk/pkg/compaction/compaction.go
Comment thread go/adk/pkg/a2a/executor.go
Comment thread go/adk/pkg/compaction/compaction_test.go
Implements post-invocation event history compaction for the Go ADK
runtime. The upstream Go ADK v1.1.0 does not ship this so it is
implemented within kagent via a new compaction package
Closes kagent-dev#1783

Signed-off-by: Yashraj Shukla <shuklayashraj68@gmail.com>
@yashrajshuklaaa yashrajshuklaaa force-pushed the feat/go-adk-compaction branch from ff168bb to 0b561b3 Compare June 6, 2026 13:34
@yashrajshuklaaa

Copy link
Copy Markdown
Author

@iplay88keys @jmhbh @supreme-gg-gg PTAL
addressed the comments of copilot cuz they are genuine
if u need any changes pls let me know

@yashrajshuklaaa yashrajshuklaaa requested a review from Copilot June 8, 2026 11:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

go/adk/pkg/runner/adapter.go:1

  • CreateRunnerConfig now has a breaking signature change by adding *compaction.Config as a new return value. If this function is used outside this package (it’s exported), this will force cascading changes for all callers. Consider a non-breaking approach such as (a) introducing a new function (e.g., CreateRunnerConfigWithCompaction) or (b) embedding compaction config into runner.Config/an options struct to avoid widening the return tuple.
package runner

Comment on lines +251 to +265
func buildSummaryEvent(summaryText string) *adksession.Event {
e := &adksession.Event{
ID: uuid.NewString(),
InvocationID: "compaction_" + uuid.NewString(),
Timestamp: time.Now(),
Author: summaryEventAuthor,
}
e.LLMResponse = model.LLMResponse{
Content: &genai.Content{
Role: "model",
Parts: []*genai.Part{{Text: summaryText}},
},
}
return e
}
Comment on lines +224 to +249
func serializeEvents(events []*adksession.Event) string {
var sb strings.Builder
for _, e := range events {
if e.Content == nil {
continue
}
role := e.Author
if role == "" {
role = e.Content.Role
}
for _, p := range e.Content.Parts {
if p == nil {
continue
}
switch {
case p.Text != "":
fmt.Fprintf(&sb, "[%s]: %s\n", role, p.Text)
case p.FunctionCall != nil:
fmt.Fprintf(&sb, "[%s] called tool %q\n", role, p.FunctionCall.Name)
case p.FunctionResponse != nil:
fmt.Fprintf(&sb, "[tool %s response]\n", p.FunctionResponse.Name)
}
}
}
return sb.String()
}
Comment on lines +72 to +90
if len(invocations) >= c.cfg.CompactionInterval {
trigger = true
log.V(1).Info("Compaction triggered by interval",
"invocations", len(invocations),
"threshold", c.cfg.CompactionInterval)
}

if !trigger && c.cfg.TokenThreshold > 0 {
tokens := lastTokens
if tokens == 0 {
tokens = estimateTokens(allEvents)
}
if tokens >= c.cfg.TokenThreshold {
trigger = true
log.V(1).Info("Compaction triggered by token threshold",
"tokens", tokens,
"threshold", c.cfg.TokenThreshold)
}
}
Comment on lines +106 to +111
keepCount := c.cfg.OverlapSize
if keepCount >= len(invocations) {
return nil
}

toCompact := invocations[:len(invocations)-keepCount]
Comment on lines +133 to +145
if err := sessionSvc.AppendEvent(ctx, sess, summaryEvent); err != nil {
return fmt.Errorf("compaction: failed to persist summary event: %w", err)
}

var keepEvents []*adksession.Event
for _, inv := range invocations[len(invocations)-keepCount:] {
keepEvents = append(keepEvents, inv.events...)
}
// Apply EventRetentionSize cap if configured.
if c.cfg.EventRetentionSize > 0 && len(keepEvents) > c.cfg.EventRetentionSize {
keepEvents = keepEvents[len(keepEvents)-c.cfg.EventRetentionSize:]
}
replaceSessionEvents(sess, summaryEvent, keepEvents)
Comment on lines +362 to +371
// 10b. Post-invocation compaction (no-op when not configured).
// TODO: avoid the extra GetSession call by threading the session object through Execute.
if e.compactor != nil && e.sessionService != nil {
liveSess, sessErr := e.sessionService.GetSession(ctx, e.appName, userID, sessionID)
if sessErr == nil && liveSess != nil {
if compactErr := e.compactor.MaybeCompact(ctx, liveSess, e.sessionService, 0); compactErr != nil {
e.logger.Error(compactErr, "Post-invocation compaction failed (non-fatal)")
}
}
}
Comment on lines +82 to +87
if text == "" {
t.Fatal("expected non-empty serialized text")
}
if len(text) == 0 {
t.Fatal("serialized text should not be empty")
}

@EItanya EItanya left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on. Adding Go-runtime compaction is a useful direction, and the current PR is a helpful starting point. I took a closer look against the Python ADK compaction path, and I think there is one important design gap to resolve before merging.

The main issue is that this does not yet match Python ADK compaction semantics. Python ADK does not durably delete compacted raw events. It appends a special compaction marker event with actions.compaction containing the compacted time range and compacted content, then prompt construction later uses that marker to replace covered raw events with the summary. In this PR, the Go runtime appends a normal model-looking summary event and trims only the in-memory local session. After a fresh GetSession, the persisted store still has the raw events plus the summary, and Go ADK has no marker-aware filtering step, so the next model request can see both raw history and the summary.

That means I would treat the durable-store Copilot concern as real, but I would not solve it by deleting old backend rows unless we intentionally want different semantics from Python. A closer match would be to persist some compaction marker/range and add a Go-side prompt-time filtering layer, or otherwise wrap/filter session events before Go ADK builds LLM contents.

A few related differences from the Python implementation also seem worth addressing:

  • compaction_interval in Python means number of new invocations since the last compaction. The current Go code checks total grouped invocations, which can repeatedly compact the same logical history after reload or behave differently depending on in-memory truncation.
  • summarizer_model appears to be parsed but not used to construct SummarizerLLM. Python builds an LlmEventSummarizer from the configured model, and otherwise initializes one from the agent model.
  • token_threshold and event_retention_size are validated as a pair in Python. The Go implementation currently accepts partial token config and applies event retention to the kept events after interval compaction too, while Python uses retention for token-threshold compaction.
  • Python takes care not to compact pending function calls or split function call/function response pairs. The Go implementation serializes calls/responses into text and can drop the structured pairing from the retained context.

The smaller Copilot comments about defensive copying, sentinels, and test cleanup are either already addressed or easy to handle. The extra GetSession call is a performance concern, but I think it is secondary to getting the compaction model aligned with Python first.

Suggested acceptance test: create enough persisted events to trigger compaction, run compaction, reload the session from the backing service, then verify that the next model request sees summary plus retained events rather than raw compacted history plus summary. That would catch the main semantic issue here.

Again, thank you for pushing this forward. I think the feature is valuable; it just needs the durable marker/filtering piece before it will behave like the existing Python ADK path.

@yashrajshuklaaa

Copy link
Copy Markdown
Author

@EItanya thanks for the detailed explanation , it really helped me understand what the correct implementation should look like
I went and read through the Python ADK source ( apps/compaction.py , flows/llm_flows/contents.py , events/event_actions.py ) to understand the marker based approach you described
that makes total sense now - the compaction event carries start_timestamp , end_timestamp and compacted_content in actions.compaction and then contents.py filters out raw events that fall within that range at prompt-build time
The raw events stay in the backing store untouched.

So i checked the Go ADK module to see if the same primitives exist there. Went through every released version from v0.1.0 all the way to v1.4.0 , searching for EventCompaction , CompactedContent , StartTimestamp and any other naming variation I could think of. Nothing. The EventActions struct in Go ADK doesn't have a Compaction field at all and contents_processor.go has no filtering step for compaction markers either

@yashrajshuklaaa

Copy link
Copy Markdown
Author

so i think the right move is to contribute to EventCompaction support upstream to google.golang.org/adk first by adding the struct to EventActions and the filtering logic in contents_processor.go to mirror what Python ADK does
once that's done coming back and implementing proper marker-based compaction in kagent would be pretty straightforward

@yashrajshuklaaa

Copy link
Copy Markdown
Author

@EItanya opened an issue at https://github.com/google/adk-go after that we can continue with this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add compaction to golang ADK

3 participants