Python: feat(core): add max_duration_seconds bound and stop_reason signal to tool loop (#7587) - #7772
Open
Thota Sai Karthik (karthik-0306) wants to merge 1 commit into
Open
Conversation
Thota Sai Karthik (karthik-0306)
temporarily deployed
to
github-app-auth
August 19, 2026 20:53 — with
GitHub Actions
Inactive
Thota Sai Karthik (karthik-0306)
temporarily deployed
to
github-app-auth
August 19, 2026 20:53 — with
GitHub Actions
Inactive
Thota Sai Karthik (karthik-0306)
temporarily deployed
to
github-app-auth
August 19, 2026 20:53 — with
GitHub Actions
Inactive
Copilot started reviewing on behalf of
Thota Sai Karthik (karthik-0306)
August 19, 2026 20:59
View session
Thota Sai Karthik (karthik-0306)
temporarily deployed
to
github-app-auth
August 19, 2026 21:01 — with
GitHub Actions
Inactive
Contributor
There was a problem hiding this comment.
Pull request overview
Adds duration bounds and machine-readable termination reasons to Python function-invocation loops.
Changes:
- Adds and validates
max_duration_seconds. - Tracks stop reasons across streaming and non-streaming paths.
- Adds tests and changelog documentation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
python/packages/core/agent_framework/_tools.py |
Implements duration tracking and stop reasons. |
python/packages/core/tests/core/test_function_invocation_logic.py |
Tests duration limits and termination signals. |
python/CHANGELOG.md |
Documents the new behavior. |
Suppressed comments (2)
python/packages/core/agent_framework/_tools.py:3528
- The streaming path has the same enforcement gap: approved calls are replayed before this check, while the call-dropping/fallback logic at lines 3449-3466 recognizes only
max_function_calls. Consequently, an expired approval or a provider-emitted call despitetool_choice="none"can still execute. Include duration expiry in a shared pre-execution and fallback predicate.
if (
max_duration_seconds is not None
and (perf_counter() - budget_state["start_time"]) >= max_duration_seconds
):
python/packages/core/agent_framework/_tools.py:3518
- The streaming branch also leaks the internal action name
"stop"as a public stop reason. This is outside the documented value set and differs from approval-time error exhaustion, which reportscompleted. Use the same documented semantic reason for consecutive-error exhaustion in both paths.
budget_state.setdefault("stop_reason", "stop")
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Thota Sai Karthik (karthik-0306)
force-pushed
the
fix-issue-7587
branch
from
August 20, 2026 18:16
0ef8220 to
05ce001
Compare
Thota Sai Karthik (karthik-0306)
deployed
to
github-app-auth
August 20, 2026 18:17 — with
GitHub Actions
Active
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
Function invocation loops in
FunctionInvocationLayer(_tools.py) currently allow capping LLM roundtrips viamax_iterationsand total function calls viamax_function_calls, but lack a wall-clock time limit. Unattended or complex agent runs can execute tools repeatedly and stall for long periods without a bounded total duration.Additionally, callers currently have no programmatic way to determine why a function invocation run ended (e.g. normal completion vs hitting
max_iterationsor a tool limit).This PR addresses #7587 by introducing
max_duration_secondstoFunctionInvocationConfigurationand surfacing a_agent_framework_stop_reasonsignal onChatResponse.additional_properties.Description & Review Guide
What are the major changes?
max_duration_secondsConfig Field: Addedmax_duration_seconds: float | NonetoFunctionInvocationConfiguration(TypedDict) and normalized validation (> 0orNone).budget_state.setdefault("start_time", perf_counter())inget_responseso duration is measured cumulatively across human approval round-trips.max_duration_secondsis exceeded mid-loop (checked after each tool batch), further tool calls are disabled (tool_choice = "none") and the model is forced to produce a final text response, reusing the establishedmax_function_callsdegradation path.stop_reasonSignal: Surface_agent_framework_stop_reasoninChatResponse.additional_propertieswith values"completed","max_iterations", or"max_duration_seconds".setdefault("stop_reason", ...)so the first triggered condition in execution order claims the reason (duration check runs before the call-count check).ResponseStreamwith_finalize_with_stop_reasonso streaming callers receive_agent_framework_stop_reasonon the finalChatResponsewithout altering individual streaming update counts.What is the impact of these changes?
response.additional_properties["_agent_framework_stop_reason"]to programmatically handle how a run concluded.max_duration_secondsisNone(unlimited).What do you want reviewers to focus on?
setdefault("stop_reason", ...)logic in both non-streaming (_get_response_with_function_invocation) and streaming (_stream_response_with_function_invocation) loops.budget_state.setdefault("start_time", ...)inget_responseproperly preserves start time across approval resumes for cumulative timing.ResponseStreamfinalizer wrapper inget_responseensuring streaming updates remain clean and untouched.Related Issue
Fixes #7587
Contribution Checklist