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

Orphaned function_call permanently and silently disables context compaction (once triggered)

Aperta
#7,236 4 commenti 0 reazioni 1 assegnatario Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
68/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
ai, backend

Direzione di ricerca

Start in src/google/adk/apps/compaction.py, especially _longest_self_contained_prefix and the two event-selection paths that call it. Run the focused tests in tests/unittests/apps/test_compaction.py and the linked deterministic reproduction before changing behavior. Done means orphaned calls no longer permanently block later compaction, while pending or cross-window calls retain the existing conservative behavior and all listed tests pass.

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

Descrizione

core

🔴 Required Information

Describe the Bug:
Once the first context compaction happens in a session, if there is an
orphaned function_call (a tool call whose function_response never
arrives — e.g. the process handling the tool died, such as a Kubernetes pod
restart while the agent was waiting on a tool), every subsequent
compaction is silently and permanently blocked
, even as the prompt keeps
growing past the configured token_threshold. There is no log and no error.

Root cause: google.adk.apps.compaction._longest_self_contained_prefix()
walks the compaction window keeping a set of open obligations keyed by call
id (function_call opens an id, the matching function_response closes
it), and only records a cut point where that set is completely empty. Once
the first compaction has happened, an orphaned call becomes the first
candidate event of every subsequent round, so the set can never empty again
— safe_length freezes at 0 forever, and _events_to_compact_for_token_threshold
(and the sliding-window path, which shares the same helper) returns [].

This affects both compaction strategies since both call the same helper.

Steps to Reproduce:

  1. Install: pip install google-adk==2.9.2
  2. Persist a session (e.g. via DatabaseSessionService) that already had a
    successful 1st compaction, followed by one orphaned function_call
    (no matching function_response anywhere in the session), followed by
    several normal, balanced turns.
  3. Reload the session and call the internal
    google.adk.apps.compaction._events_to_compact_for_token_threshold(...)
    (or exercise the sliding-window path) on it.
  4. Observe the returned list is empty — no compaction — even though there
    are dozens of eligible events after the orphan.

A minimal, deterministic, runnable reproduction (no LLM call / API key
needed — the bug lives entirely in the pure event-selection logic) is here:
https://github.com/erickleal-azos/simulate-adk-fc-orphan

git clone https://github.com/erickleal-azos/simulate-adk-fc-orphan
cd simulate-adk-fc-orphan
poetry install
poetry run python repro_orphan_function_call.py

Expected Behavior:
An orphaned function_call from an already-finished invocation, with no
response anywhere in the session and not a long-running/pending
auth/tool-confirmation call, should eventually be treated as unanswerable
so that compaction can resume over the healthy events that follow it
(optionally with a warning log, since today the stall is silent).

Observed Behavior:
_events_to_compact_for_token_threshold (and the sliding-window variant)
returns [] forever after the orphan, so the session's prompt never
compacts again and grows without bound. Example from the linked repro:

Scenario A -- NO orphaned function_call (baseline)
Baseline: events_to_compact = 36 event(s)
  -> 2nd compaction would proceed normally.

Scenario B -- orphaned function_call right after the 1st compaction
Buggy: events_to_compact = 0 event(s)
  -> BLOCKED: no 2nd compaction, even with 42 eligible events in the session.

Scenario B (healed) -- removing only the orphaned call event
Healed: events_to_compact = 36 event(s)
  -> 2nd compaction would proceed normally.

Environment Details:

  • ADK Library Version (pip show google-adk): 2.9.2 (verified byte-identical on main, so also present on latest)
  • Desktop OS: Linux (also observed in a GKE/Kubernetes)
  • Python Version (python -V): 3.11

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: N/A — the bug is in pure event-selection logic (google.adk.apps.compaction), no LLM call is involved in triggering or reproducing it.

🟡 Optional Information

Regression:
Not a regression from a specific prior version as far as I can tell — the
guard this interacts with was added in PR #5832 (fixing #5602, "Compaction
breaks LongRunningFunctionTool resume") and is deliberate/tested for the
first stall. What wasn't accounted for is that the stall is unbounded:
once the window's start point is pinned at last_compacted_end_timestamp,
an orphaned call there blocks that window from ever balancing again.

Logs:
None — this is precisely the problem: the block is completely silent, no
log or error is emitted, which made the original incident hard to diagnose.

Additional Context:
This was found in a real staging incident: a Kubernetes pod running the
tool-execution side was restarted while an agent was waiting on a tool call.
The session came back with a function_call that would never be answered,
and from that point on it never compacted again.

I have a proposed fix with a 7-scenario validation matrix (including the
two existing upstream tests that encode the conservative window,
test_sliding_window_excludes_pending_function_call_events and
test_sliding_window_plain_orphaned_function_call_dropped_from_contents,
replayed to confirm no regression) here:
https://github.com/erickleal-azos/simulate-adk-fc-orphan#the-proposed-solution

Summary of the approach: keep the strict rule exactly as-is (if it yields a
non-empty prefix, return it untouched — behavior for the normal/healthy
case does not change). Only when it yields nothing (the stall) do we ask a
second question — is each open obligation still fulfillable? An id keeps
blocking if: (1) a matching function_response exists anywhere in the
session (not just the window, to avoid splitting a pair across the window
boundary when the response lives past the window's end — this is what the
original guard protects), (2) it is in Event.long_running_tool_ids or a
pending auth/tool-confirmation request, or (3) it was opened by the newest
invocation in the session (may still be in flight). Everything else is a
normal tool call from an already-finished invocation with no response
anywhere — provably dead, since a normal tool always emits a response event
(including on error), so its absence means the process died holding the
obligation. Event.long_running_tool_ids is persisted in its own DB column
and survives the kind of restart that causes this, which the fix relies on.

I'm open to opening a PR against src/google/adk/apps/compaction.py with
this fix, if the maintainers agree the approach makes sense — just let me
know and I'll follow up with tests in tests/unittests/apps/test_compaction.py.

Minimal Reproduction Code:
See https://github.com/erickleal-azos/simulate-adk-fc-orphan/blob/main/repro_orphan_function_call.py
for the full runnable script. Core of the buggy helper it exercises
(google/adk/apps/compaction.py):

open_ids: set[str] = set()
safe_length = 0
for index, event in enumerate(events):
  open_ids -= _event_function_response_ids(event)
  open_ids |= _event_function_call_ids(event)
  if event.actions:
    open_ids |= set(event.actions.requested_tool_confirmations)
    open_ids |= set(event.actions.requested_auth_configs)
  if not open_ids:
    safe_length = index + 1
return events[:safe_length]

How often has this issue occurred?:

  • Rare (<1%) — requires the process handling a tool call to die or be
    killed mid-call (pod restart, OOM kill, crash) after at least one
    compaction has already happened in the session. In practice an
    uncommon window, but when it does line up the block is permanent and
    silent.
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.