Skip to content

refactor(tools): extract supervised batch execution engine - #207

Merged
elkaix merged 12 commits into
mainfrom
refactor/tool-execution-engine
Jul 16, 2026
Merged

refactor(tools): extract supervised batch execution engine#207
elkaix merged 12 commits into
mainfrom
refactor/tool-execution-engine

Conversation

@elkaix

@elkaix elkaix commented Jul 15, 2026

Copy link
Copy Markdown
Member

Related Issue

No linked issue; implements the approved third phase of the provider/stream/tool-execution design.

Description

  • add an optional runtime-checkable core BatchToolset/ToolBatchHandle protocol while preserving third-party Toolset.handle() compatibility
  • dispatch tools only after terminal response assembly and expose ordered results, completed-result snapshots, explicit cancellation, and immutable batch summaries through StepResult
  • extract execution preparation, deduplication, reader/writer scheduling, hooks, callbacks, and task ownership into a private ToolExecutionEngine
  • keep PythinkerToolset responsible for registry, visibility, dependency injection, and MCP lifecycle
  • bound cancellation at five seconds, fail closed while late work drains, and recover automatically after supervised settlement
  • route PythinkerSoul through ToolBatchContext and ToolBatchSummary instead of execution internals
  • publish matched before/after all/5 characterization reports and document the measured local overhead without making a performance claim

Failure and compatibility guarantees

  • batch construction is exception-atomic: no tool starts before a supervising handle is returned
  • callbacks publish in completion order; results() stays in model call order
  • repeated caller cancellation cannot detach cleanup
  • timeout, poison, partial completion, and recovery remain explicit
  • legacy per-call rollback/callback behavior from PR fix(core): correlate streamed tool calls safely #205 remains covered
  • no persisted schema, dependency, telemetry endpoint, credential routing, or provider behavior changed

Verification

  • make check-pythinker-core
  • make test-pythinker-core — 429 passed
  • make check-pythinker-code
  • make test-pythinker-code — 7,063 root passed; 65 e2e passed
  • characterization — 18 matched scenarios per revision; zero leaked tasks/processes/sessions; cancellation/recovery green; decision states unchanged

Measured local medians increased by 3.6–14.5% for execution fixtures, 19.4–20.7% for dedupe fixtures, and 3.8–4.2% for registry projection. Raw samples and the residual-risk assessment are tracked under docs/superpowers/reports/2026-07-15-tool-execution-*.

Checklist

  • I have read the CONTRIBUTING document.
  • I have added tests that prove the feature and failure paths work.
  • I updated CHANGELOG.md and generated the docs changelog with cd docs && npm run sync.
  • I updated architecture and characterization documentation.

Summary by CodeRabbit

  • New Features
    • Supervised batch tool execution now produces ordered, deduplicated results while preserving legacy per-call behavior.
    • Step results now include batch execution summaries, a completed-results snapshot, and support for canceling tool execution.
    • Tool-result callbacks now support both synchronous and async handlers; callback failures are reported without stopping result collection.
  • Bug Fixes
    • Improved cancellation/timeout handling with bounded recovery and safer cleanup behavior for late work.
  • Documentation
    • Added contributor guidance for deterministic tool-execution benchmarking/characterization and evidence/cleanup expectations.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f8acd0d-7deb-45ec-a997-5c8126442c75

📥 Commits

Reviewing files that changed from the base of the PR and between d9bab1d and c4b759f.

⛔ Files ignored due to path filters (3)
  • docs/superpowers/plans/2026-07-15-tool-execution-cancellation-state-rollback.md is excluded by !docs/**
  • tasks/lessons.md is excluded by !tasks/**
  • tasks/todo.md is excluded by !tasks/**
📒 Files selected for processing (2)
  • src/pythinker_code/soul/toolset.py
  • tests/core/test_tool_execution_cancellation.py

📝 Walkthrough

Walkthrough

Tool execution is centralized in a supervised batch engine with ordered results, callbacks, deduplication, bounded cancellation, and late-work cleanup. Core step handling, PythinkerToolset, and PythinkerSoul exchange batch contexts, result snapshots, and execution summaries.

Changes

Tool execution batching

Layer / File(s) Summary
Batch API and StepResult lifecycle
packages/pythinker-core/src/pythinker_core/tooling/__init__.py, packages/pythinker-core/src/pythinker_core/__init__.py, packages/pythinker-core/tests/test_batch_toolset.py
Batch contracts, summaries, callback supervision, ordered results, cancellation settlement, and legacy compatibility are added and tested.
Execution engine and facade delegation
src/pythinker_code/soul/tool_execution.py, src/pythinker_code/soul/toolset.py, tests/core/test_tool_execution_engine.py, tests/core/test_tool_execution_cancellation.py
Tool preparation, gating, deduplication, hooks, telemetry, batch supervision, timeout poisoning, recovery, and facade delegation are implemented and tested.
Soul batch context and result snapshots
src/pythinker_code/soul/pythinkersoul.py, tests/core/test_pythinkersoul_stuck_loop.py, tests/core/test_pythinkersoul_turn_balance.py
PythinkerSoul passes cross-step fingerprints, reads completed results from StepResult, and updates deduplication and repeat-loop state from batch summaries.
Documentation and logging validation
CONTRIBUTING.md, CHANGELOG.md, tests/core/test_session_logging.py
Tool-execution characterization guidance, changelog text, and delegated execution logging assertions are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PythinkerSoul
  participant pythinker_core.step
  participant PythinkerToolset
  participant ToolExecutionEngine
  participant _ExecutionBatch
  participant Tool
  PythinkerSoul->>pythinker_core.step: pass ToolBatchContext
  pythinker_core.step->>PythinkerToolset: handle_batch(tool calls)
  PythinkerToolset->>ToolExecutionEngine: delegate batch
  ToolExecutionEngine->_ExecutionBatch: prepare and supervise
  _ExecutionBatch->>Tool: execute calls
  Tool-->>_ExecutionBatch: return ToolResult
  _ExecutionBatch-->>pythinker_core.step: ordered results and summary
  pythinker_core.step-->>PythinkerSoul: StepResult
Loading

Possibly related PRs

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.89% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required conventional-commit format and accurately summarizes the refactor.
Description check ✅ Passed The description covers the required sections with detailed change, verification, and checklist information, with only minor template deviations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/tool-execution-engine

Comment @coderabbitai help to get the list of available commands.

Comment thread src/pythinker_code/soul/tool_execution.py Fixed
Comment thread packages/pythinker-core/src/pythinker_core/tooling/__init__.py
Comment thread packages/pythinker-core/src/pythinker_core/tooling/__init__.py
Comment thread packages/pythinker-core/src/pythinker_core/tooling/__init__.py
Comment thread packages/pythinker-core/src/pythinker_core/tooling/__init__.py
Comment thread packages/pythinker-core/src/pythinker_core/tooling/__init__.py
Comment thread packages/pythinker-core/src/pythinker_core/tooling/__init__.py
Comment thread packages/pythinker-core/tests/test_batch_toolset.py
Comment thread packages/pythinker-core/tests/test_batch_toolset.py
Comment thread packages/pythinker-core/tests/test_batch_toolset.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/pythinker-core/src/pythinker_core/__init__.py`:
- Around line 94-96: Update the async callback handling around
inspect.isawaitable and async_callback_done so created tasks are registered with
the batch/StepResult supervisor instead of detached via ensure_future. Ensure
tool_results() settlement drains and cancel_tool_execution() cancels these
tasks, preserving synchronous callback behavior and confirming no tasks remain
leaked.

In `@packages/pythinker-core/tests/test_batch_toolset.py`:
- Around line 433-463: Remove the unused started_work field and its trivially
true assertion from ConstructionFailureToolset and
test_batch_construction_failure_is_side_effect_free. Rename the test to reflect
failure propagation and callback suppression, while retaining the RuntimeError
expectation and callbacks == [] assertion.

In `@src/pythinker_code/soul/tool_execution.py`:
- Around line 849-856: Update the BaseException cleanup in PythinkerSoul so
watcher tasks whose corresponding source futures are already complete are not
cancelled; disable callbacks for them, then gather them to let _watch() copy
their results into _completed_results. Continue cancelling watchers tied to
unfinished sources and preserve the existing source-future cancellation and
gathering behavior.
- Around line 876-891: Update _bounded_settlement to check
self._supervisor.done() before invoking asyncio.wait_for, allowing an
already-settled supervisor to complete immediately when timeout is zero without
raising ToolCancellationTimeoutError. Preserve the existing timed wait and
cancellation-timeout handling for unsettled supervisors, and add a regression
test covering timeout=0 with an already-completed supervisor.

In `@tests/core/test_tool_execution_engine.py`:
- Around line 70-80: Decouple the tests from private implementation details: in
tests/core/test_tool_execution_engine.py lines 70-80, exercise handle() through
a real test tool or supported public seam instead of patching _execution.handle;
in tests/core/test_session_logging.py lines 208-208 and 233-233, capture and
assert the emitted error and warning logs rather than patching
tool_execution.logger.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a71a1c18-4f48-428c-a849-95e331bc5cc6

📥 Commits

Reviewing files that changed from the base of the PR and between 10aedf2 and bbe28b2.

⛔ Files ignored due to path filters (5)
  • docs/en/customization/architecture.md is excluded by !docs/**
  • docs/en/release-notes/changelog.md is excluded by !docs/**
  • docs/superpowers/reports/2026-07-15-tool-execution-after.json is excluded by !docs/**
  • docs/superpowers/reports/2026-07-15-tool-execution-before.json is excluded by !docs/**
  • docs/superpowers/reports/2026-07-15-tool-execution-engine-characterization.md is excluded by !docs/**
📒 Files selected for processing (13)
  • CHANGELOG.md
  • CONTRIBUTING.md
  • packages/pythinker-core/src/pythinker_core/__init__.py
  • packages/pythinker-core/src/pythinker_core/tooling/__init__.py
  • packages/pythinker-core/tests/test_batch_toolset.py
  • src/pythinker_code/soul/pythinkersoul.py
  • src/pythinker_code/soul/tool_execution.py
  • src/pythinker_code/soul/toolset.py
  • tests/core/test_pythinkersoul_stuck_loop.py
  • tests/core/test_pythinkersoul_turn_balance.py
  • tests/core/test_session_logging.py
  • tests/core/test_tool_execution_cancellation.py
  • tests/core/test_tool_execution_engine.py

Comment thread packages/pythinker-core/src/pythinker_core/__init__.py Outdated
Comment thread packages/pythinker-core/tests/test_batch_toolset.py
Comment thread src/pythinker_code/soul/tool_execution.py
Comment thread src/pythinker_code/soul/tool_execution.py
Comment thread tests/core/test_tool_execution_engine.py Outdated
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.94197% with 52 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pythinker_code/soul/tool_execution.py 89.14% 34 Missing and 18 partials ⚠️

📢 Thoughts on this report? Let us know!

Comment thread packages/pythinker-core/tests/test_batch_toolset.py Fixed
Comment thread packages/pythinker-core/tests/test_batch_toolset.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pythinker_code/soul/toolset.py (1)

1071-1088: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve caller cancellation until after MCP teardown.

If cancellation lands during self._execution.cleanup(), CancelledError bypasses _close(), leaving MCP session holders and clients open. Capture it alongside the timeout, finish teardown, then re-raise it.

Proposed fix
-        execution_error: ToolCancellationTimeoutError | None = None
+        execution_error: BaseException | None = None
         try:
             await self._execution.cleanup()
-        except ToolCancellationTimeoutError as error:
+        except (asyncio.CancelledError, ToolCancellationTimeoutError) as error:
             execution_error = error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pythinker_code/soul/toolset.py` around lines 1071 - 1088, Update the
cleanup flow in the enclosing teardown method to catch and store
asyncio.CancelledError alongside ToolCancellationTimeoutError from
self._execution.cleanup(). Always complete the existing MCP _close teardown and
gather operations, then re-raise the captured cancellation after teardown,
preserving the current timeout-error behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/pythinker_code/soul/toolset.py`:
- Around line 1071-1088: Update the cleanup flow in the enclosing teardown
method to catch and store asyncio.CancelledError alongside
ToolCancellationTimeoutError from self._execution.cleanup(). Always complete the
existing MCP _close teardown and gather operations, then re-raise the captured
cancellation after teardown, preserving the current timeout-error behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 302d48b8-3168-45ae-99bb-90f5dc472a86

📥 Commits

Reviewing files that changed from the base of the PR and between 3012b75 and d9bab1d.

⛔ Files ignored due to path filters (5)
  • docs/en/customization/agent-architecture.md is excluded by !docs/**
  • docs/superpowers/plans/2026-07-15-tool-execution-cancellation-state-rollback.md is excluded by !docs/**
  • docs/superpowers/specs/2026-07-15-tool-execution-cancellation-state-rollback-design.md is excluded by !docs/**
  • tasks/lessons.md is excluded by !tasks/**
  • tasks/todo.md is excluded by !tasks/**
📒 Files selected for processing (7)
  • packages/pythinker-core/src/pythinker_core/__init__.py
  • packages/pythinker-core/tests/test_batch_toolset.py
  • src/pythinker_code/soul/pythinkersoul.py
  • src/pythinker_code/soul/tool_execution.py
  • src/pythinker_code/soul/toolset.py
  • tests/core/test_pythinkersoul_turn_balance.py
  • tests/core/test_tool_execution_cancellation.py

@elkaix
elkaix merged commit 6349497 into main Jul 16, 2026
39 checks passed
@elkaix
elkaix deleted the refactor/tool-execution-engine branch July 16, 2026 13:45
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