Skip to content

Keep Tool calls stable across provider history and async polls - #952

Draft
Y1fe1Zh0u wants to merge 3 commits into
dataelement:002-tool-runtime-contractfrom
Y1fe1Zh0u:fix/tool-runtime-p0
Draft

Keep Tool calls stable across provider history and async polls#952
Y1fe1Zh0u wants to merge 3 commits into
dataelement:002-tool-runtime-contractfrom
Y1fe1Zh0u:fix/tool-runtime-p0

Conversation

@Y1fe1Zh0u

Copy link
Copy Markdown
Collaborator

What changed

  • keep Gemini Tool Results paired with the Tool Call from the same assistant exchange, even when providers reuse call IDs across turns
  • keep synthetic async poll calls on the original accepted Tool contract without rebuilding the ToolProvider
  • add regressions for duplicate Gemini IDs and repeated pending poll recovery

Why

Gemini previously resolved Tool Result names through a whole-history ID map, so a later duplicate ID could rename an earlier result. Async poll calls also used a new Assistant and Call ID while retaining the original frozen StepToolContext, causing tool_context_corrupt on resume.

The fix stays local: Gemini uses exchange-local identity, while async polls record their originating accepted Call ID and temporarily derive execution data from the existing frozen context. No new lifecycle state, database schema, scheduler, cancellation, or reconciliation machinery is introduced.

Impact

  • prevents Tool Result identity corruption in Gemini payloads
  • allows pending Tool operations to be polled repeatedly and completed without reloading the Tool workset
  • preserves the original batch context for subsequent Tool calls

Validation

  • 130 scoped Runtime Tool, node executor, LLM, and runtime schema tests passed
  • scoped Ruff passed
  • git diff --check passed

Stack

This draft targets 002-tool-runtime-contract because the frozen StepToolContext implementation is currently in PR #945 and has not yet merged into main.

Set the existing protocol repair, safe-read replay, and model-visible Tool episode limits to ten while preserving their current independent state and execution semantics. Update focused tests and planning artifacts to make the off-by-one behavior explicit.

Constraint: Tool-related retry and repair limits must be ten without restructuring the existing counters

Rejected: Unify protocol, Receipt, and model-visible repair state now | counter redesign is intentionally deferred

Confidence: high

Scope-risk: moderate

Directive: Keep the independent counters until the planned repair-control refactor; do not infer identical attempt semantics from the shared numeric limit

Tested: 911 Runtime and Tool pytest cases; scoped Ruff; fatal-level caller Ruff; py_compile; git diff --check

Not-tested: Live Provider credentials
Gemini reuses provider call identifiers across assistant turns, so a history-wide lookup allowed later calls to overwrite earlier tool names. Scope name resolution to the active assistant exchange while preserving provider wire identifiers.

Constraint: Gemini provider call IDs are not globally unique across turns

Rejected: Persist globally unique provider IDs | would alter provider wire identity and conflict with existing Runtime correlation

Confidence: high

Scope-risk: narrow

Directive: Keep Tool Result name resolution exchange-local when modifying Gemini history lowering

Tested: backend/tests/test_llm_single_step.py; LLM scoped tests; Ruff

Not-tested: Live Gemini API
Synthetic poll calls belong to the original accepted Tool call but use a new runtime call ID. Record the origin call ID on the synthetic Assistant message and derive the poll execution entry from the frozen StepToolContext without replacing checkpoint state.

Constraint: Keep the fix local to the reproduced async poll context mismatch.

Rejected: Add a separate async lifecycle state machine | unnecessary for preserving the accepted Tool contract.

Confidence: high

Scope-risk: narrow

Directive: Do not resolve the ToolProvider again for a poll generated from an accepted Tool call.

Tested: 130 scoped Runtime Tool, node executor, LLM, and runtime schema tests; scoped Ruff; git diff --check.

Not-tested: Real MCP provider and process-restart end-to-end execution.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant