Skip to content

Stabilize the v1.11.4 Tool execution contract - #959

Merged
Y1fe1Zh0u merged 9 commits into
dataelement:v1.11.4from
Y1fe1Zh0u:fix/tool-runtime-eight-v1.11.4
Aug 14, 2026
Merged

Stabilize the v1.11.4 Tool execution contract#959
Y1fe1Zh0u merged 9 commits into
dataelement:v1.11.4from
Y1fe1Zh0u:fix/tool-runtime-eight-v1.11.4

Conversation

@Y1fe1Zh0u

Copy link
Copy Markdown
Collaborator

Summary

  • make frozen MCP execution bindings control dispatch and reject route drift
  • keep Gemini Tool results paired with the correct assistant exchange
  • preserve accepted Tool context across async polls and legacy wait boundaries
  • expose actionable Tool failures through provider-native error representations
  • enforce the JSON Schema constraints currently advertised by builtin Tools
  • align Runtime deadlines with network and image-provider budgets
  • expose manual reconciliation for unknown dynamic MCP writes

Original findings covered

  1. Execution Binding did not control execution routing
  2. Gemini reused provider call IDs across turns incorrectly
  3. Async poll calls could leave the frozen Tool context
  4. Unknown MCP outcomes had no recovery outlet
  5. Tool failure status and repair text were weakened before reaching the model
  6. Advertised JSON Schema constraints were silently ignored
  7. Runtime deadlines could expire before declared provider budgets
  8. Legacy early-return paths dropped reconstructed StepToolContext

Validation

  • full backend suite: 2357 passed, 9 warnings
  • combined core Tool/Model/LLM regression: 363 passed
  • Ruff passed for changed Python files except pre-existing lint debt in the monolithic agent_tools.py
  • git diff --check passed

Boundaries

  • no database migration and no new dependency
  • MCP credentials and live authorization remain execution-time checks
  • unknown external writes are never automatically replayed
  • no live external MCP provider E2E was run

Integration

This release PR combines the focused work from #952, #953, #954, #955, #956, #957, and #958 on top of v1.11.4.

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.
Runtime Tool results already retain sanitized failure summaries and optional remediation, but the model boundary discarded the failure signal and remediation text. Carry one provider-neutral error bit, render actionable failure text, and map it to native Anthropic and Gemini representations.

Constraint: Keep Runtime control fields such as model_action and side_effect_state out of the model protocol.

Rejected: Serialize the complete internal Tool outcome | couples provider prompts to Runtime control metadata.

Confidence: high

Scope-risk: narrow

Directive: Expected Tool failures should provide sanitized result_summary and safe_remediation at their source.

Tested: 151 scoped model-step, provider-boundary, and Tool-step tests; scoped Ruff; git diff --check.

Not-tested: Live Anthropic, Gemini, and OpenAI provider calls.
A model-visible MCP definition now carries a secret-free execution binding into the durable Tool context. The Tool step dispatches through that binding, resolves the exact assignment, and rejects route changes instead of re-selecting an endpoint by name. Readiness and binding creation share one database query, while provider payloads remain standard Tool schemas.

Constraint: Live authorization, enablement, and credential values must still be checked at execution time.

Rejected: Freeze decrypted credentials in the checkpoint | would persist secrets and prevent revocation or rotation.

Rejected: Refactor the complete Tool registry | unnecessary for closing the execution-route correctness gap.

Confidence: high

Scope-risk: moderate

Directive: Do not send _runtime_binding to model providers or replace exact assignment checks with name lookup.

Tested: 969 Tool, Model Step, and agent_tools pytest cases; scoped Ruff; py_compile; git diff --check.

Not-tested: Live MCP provider call and production database process restart.
The Runtime validator now enforces the JSON Schema keywords already used by production Tool definitions, so arguments rejected by the advertised contract cannot reach a handler. Failures remain bounded, value-free, and use the existing repairable Tool result path.

Constraint: No new dependency and no attempt to implement the entire JSON Schema specification.

Rejected: Add a general JSON Schema package | broader dependency and behavior change than required by the current Tool catalog.

Confidence: high

Scope-risk: moderate

Directive: Add validator support when introducing a new schema keyword; unsupported schema contracts must not silently pass.

Tested: 118 Runtime Tool validation, Tool Step, and builtin contract tests; scoped Ruff; git diff --check.

Not-tested: Property-based comparison against a complete JSON Schema implementation.
Network reads and image generation now carry deadline policies that cover their declared provider timeout. Custom image generation retains its existing 600 second configurable upper bound instead of being cancelled by the generic Runtime default.

Constraint: Provider-specific internal timeouts may remain shorter, but the Runtime wrapper must not expire first.

Rejected: Read live Tool configuration inside the Tool Step deadline resolver | would reintroduce mutable routing policy after call acceptance.

Confidence: high

Scope-risk: narrow

Directive: Keep builtin timeout declarations and Runtime deadline policy tests aligned when changing provider budgets.

Tested: 171 Tool contract, Model Step, Tool Step, and builtin contract tests; scoped Ruff; git diff --check.

Not-tested: Wall-clock calls to web and image providers.
Legacy checkpoints now return the reconstructed StepToolContext from every ordinary wait, unknown, A2A, group failure, and error exit. A resumed Tool node therefore reuses the accepted Workset instead of querying the current ToolProvider again.

Constraint: Async poll continuation remains owned by the dedicated async-context repair.

Rejected: Mutate graph state directly inside ToolStepService | would bypass the existing ToolStepResult checkpoint boundary.

Confidence: high

Scope-risk: narrow

Directive: Any new ToolStep early return after legacy context resolution must propagate step_tool_context.

Tested: 68 Tool Step tests plus targeted legacy unknown and A2A resume regressions; scoped Ruff; git diff --check.

Not-tested: Production checkpoint database restart and legacy approval fixture outside current canonical schemas.
Unknown dynamic MCP receipts now use the existing audited Direct Chat reconciliation flow. A user must confirm whether the external action happened before the old receipt is settled and the Run may resume; the Runtime never replays the original MCP call automatically.

Constraint: Dynamic MCP Tools are conservatively classified external_write with retry policy never.

Rejected: Automatically retry an unknown MCP call | could duplicate an external side effect.

Rejected: Add a separate MCP reconciler service | unnecessary while explicit human confirmation provides a real recovery outlet.

Confidence: high

Scope-risk: narrow

Directive: Do not make unknown MCP receipts retryable without an explicit idempotency contract.

Tested: 47 Tool ledger and Direct Chat runtime-state tests; 17 targeted unknown/reconciliation tests; scoped Ruff; git diff --check.

Not-tested: Browser UI interaction and a live MCP server with an ambiguous transport outcome.
The integrated Tool fixes expose two stale assertions in the release test suite: network reads now use the declared 60-second Runtime budget, and the durable execution model already includes provider identity and contract version columns. Align those assertions and remove a duplicate import so the combined release branch can be verified as one unit.

Constraint: Preserve the eight reviewed fix commits without folding or rewriting their history
Rejected: Ignore the full-suite failures | they would leave the integration PR without a clean release-level regression result
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep deadline and schema assertions synchronized with the production Tool contract
Tested: Full backend suite, 2357 passed; scoped Ruff excluding pre-existing agent_tools.py debt; git diff --check
Not-tested: Live external MCP provider execution
@Y1fe1Zh0u
Y1fe1Zh0u merged commit aa808a0 into dataelement:v1.11.4 Aug 14, 2026
1 check failed
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