Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto
#7,236 4 comentarios 0 reacciones 1 asignado Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
68/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
ai, backend

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.
Lenguaje dominante
Python
Estrellas
21.6k
Forks
4k
Merge medio
13 h 49 min
PR fusionados (30 d)
10

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de google/adk-python

Todos los issues de google/adk-python

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.