GoogleCloudPlatform / GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK

adk-2.0 design: pause registry read-after-write strategy

Open
#206 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
47
Forks
21
Avg merge
2d 13h
Merged PRs (30d)
33

Description

Parent tracker: #190 (v15 contract).
Wave: 3 — Design blocker.
Blocks: #199 (TOOL_PAUSED/TOOL_COMPLETED implementation).

## Problem

The pause registry contract (#190 v15) requires reconstructing matches for cross-invocation `TOOL_PAUSED` ↔ `TOOL_COMPLETED` pairs. Composite key: `(app_name, user_id, session_id, function_call_id)`. The durable source of truth is BigQuery `TOOL_PAUSED` rows + ADK in-session history.

But: rows written via the Storage Write API have **eventual visibility** — a `function_response` arriving seconds after the matching `TOOL_PAUSED` was streamed can read back zero matches and emit a false `pause_orphan`. The contract requires `pause_orphan = true` only after the chosen visibility strategy fails, never on a bare immediate-BQ-miss.

## Resolve with one of

(a) **In-process cache + BigQuery durable fallback after a settling delay.** Cache holds recent unresolved pauses in memory; BigQuery is consulted on cache miss after a tunable delay. Survives restart by falling through to BQ.

(b) **Reconstruction from in-session ADK history events that carry `long_running_tool_ids`.** Read pauses from the session log rather than BQ. Avoids the eventual-visibility window entirely.

(c) **Settling-time grace window before declaring orphans.** Hold completions for N seconds before consulting BQ; only mark orphan if no match after the window.

## Acceptance

- [ ] One strategy chosen and documented in this issue + reflected in #199 acceptance.
- [ ] `BQAA_PAUSE_REGISTRY_TTL_HOURS` and any new env vars (settling delay, cache size) documented.
- [ ] Test: stream a `TOOL_PAUSED`, then immediately observe a matching `function_response`, and verify the row is **not** marked `pause_orphan = true`.
- [ ] Test: stream a `function_response` with no prior `TOOL_PAUSED` and verify `pause_orphan = true` after the strategy's full timeout.

## References

- #190 (v15 producer §6; v9 streaming-visibility caveat; v10 layered source-of-truth).

---

## Selected approach (v4)

This section captures the decision reached in the #297 v4 review thread, so implementation assignees don't re-litigate the option list.

### Algorithm on `function_response` arrival

1. **Same-session check** via the session service's unbounded view:

```python
session = await invocation_context.session_service.get_session(
app_name=invocation_context.app_name,
user_id=invocation_context.user_id,
session_id=invocation_context.session.id,
config=GetSessionConfig(num_recent_events=None),
)
```

If `session is not None`, scan `session.events` for an Event where `response.id ∈ event.long_running_tool_ids` AND a matching `part.function_call.id == response.id` exists in `event.content.parts`.

- **Hit → not orphan.** Done. No BigQuery read required.
- **Soft miss → step 2.** Any failure mode counts as a soft miss: `None` session, raised exception, empty/trimmed/compacted history, no matching event. The producer does NOT attempt to prove history completeness — completeness is unobservable in the general case.

2. **BigQuery settling read** after `BQAA_PAUSE_REGISTRY_SETTLING_SECONDS` (default `30`). Query `TOOL_PAUSED` by composite key `(attributes.adk.app_name, user_id, session_id, attributes.adk.function_call_id)`. Hit → not orphan.

3. **Both miss → `pause_orphan = true`.**

Critically: do NOT rely on `InvocationContext._find_matching_function_call()` (`agents/invocation_context.py:485`) — it scans only `_get_events(current_invocation=True)[:-1]`, which cannot prove a true cross-invocation long-running resume.

### Env vars

- `BQAA_PAUSE_REGISTRY_SETTLING_SECONDS` (default `30`) — settling delay before consulting BigQuery.
- `BQAA_PAUSE_REGISTRY_TTL_HOURS` (already in scope) — TTL for any caching layer if added.

### Acceptance tests

- **Same-session cross-invocation**: pause in invocation N, resume in invocation N+1, same worker. `session_service.get_session(num_recent_events=None)` finds the pause; zero BigQuery reads; row is not marked orphan.
- **Trimmed-history regression**: active invocation was created with `RunConfig.get_session_config = GetSessionConfig(num_recent_events=1)`, so `invocation_context.session.events` does NOT contain the pause. The registry MUST consult the unbounded `get_session(num_recent_events=None)` AND/OR fall through to BQ. **The test does not assert history completeness** — only that a bare `invocation_context.session.events` miss cannot mark orphan.
- **Soft-miss cross-process**: stub `get_session` to return `None`. Pause exists in BQ. Settling-time BQ read finds it → not orphan.
- **True orphan**: stub `get_session` to return a session with no matching event; BQ also has none. After `BQAA_PAUSE_REGISTRY_SETTLING_SECONDS` → `pause_orphan = true`.

### References

- #297 v4 — https://github.com/GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK/issues/297#issuecomment-4647000786

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.