Repository navigation
Python: Python + .NET: add AgentBudget for autonomous loop resource limits - #8994
Closed
Mark Licata (marklicata) wants to merge 3 commits into
Closed
Mark Licata (marklicata) wants to merge 3 commits into
Mark Licata (marklicata) wants to merge 3 commits into
Conversation
Mark Licata (marklicata)
temporarily deployed
to
github-app-auth
October 2, 2026 23:45 — with
GitHub Actions
Inactive
Mark Licata (marklicata)
temporarily deployed
to
github-app-auth
October 2, 2026 23:53 — with
GitHub Actions
Inactive
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Wall-clock limits do not interrupt hung work, and the current API baselines reference a missing .NET signal evaluator.
Review effort: Balanced
Findings: 4
Open (13)
Propagate cancellation deadlines through inner execution · New Remove unresolved SignalProtocolLoopEvaluator API declarations · New Reject non-finite max_duration values · New Enforce cancellable wall-clock deadline around downstream execution · New Validate positive MaxTokens and MaxDuration values · New Test streaming budgets and missing total token counts · New Add streaming and input/output token budget tests · New Scan only assistant messages for completion tokens · New Preserve message boundaries when scanning completion tokens · New Ensure additional_properties fallback returns only strings · New Remove changelog changes from the feature PR · New Update Agent Loop guidance for the new public API · New Mark AgentBudget with the experimental harness feature decorator · New
What changed in this PR
Adds cross-language token and duration limits for autonomous agent loops, alongside typed loop-exit metadata.
Changes:
- Adds Python
AgentBudgetand .NET budget options. - Tracks cumulative token usage and elapsed time.
- Adds exit reasons, public exports, API baselines, and tests.
| File | Description |
|---|---|
python/packages/core/tests/core/test_harness_loop.py |
Adds signal and budget tests. |
python/packages/core/agent_framework/_harness/_signals.py |
Defines signal parsing and exit reasons. |
python/packages/core/agent_framework/_harness/_loop.py |
Implements Python budget enforcement. |
python/packages/core/agent_framework/__init__.pyi |
Exports new typed APIs. |
python/packages/core/agent_framework/__init__.py |
Exports runtime APIs. |
python/CHANGELOG.md |
Adds a feature entry. |
dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/LoopAgentBudgetTests.cs |
Adds .NET budget tests. |
dotnet/src/Microsoft.Agents.AI/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt |
Updates netstandard API baseline. |
dotnet/src/Microsoft.Agents.AI/PublicAPI/net9.0/PublicAPI.Unshipped.txt |
Updates .NET 9 API baseline. |
dotnet/src/Microsoft.Agents.AI/PublicAPI/net8.0/PublicAPI.Unshipped.txt |
Updates .NET 8 API baseline. |
dotnet/src/Microsoft.Agents.AI/PublicAPI/net472/PublicAPI.Unshipped.txt |
Updates .NET Framework API baseline. |
dotnet/src/Microsoft.Agents.AI/PublicAPI/net10.0/PublicAPI.Unshipped.txt |
Updates .NET 10 API baseline. |
dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopExitReason.cs |
Adds typed exit-reason constants. |
dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgentOptions.cs |
Adds budget configuration properties. |
dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgent.cs |
Implements .NET budget enforcement. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+218
to
+222
| if (budgetStopwatch is not null && budgetStopwatch.Elapsed >= this._maxDuration) | ||
| { | ||
| AgentResponse budgetResult = this.BuildResult(response, transcript, aggregatedUsage); | ||
| (budgetResult.AdditionalProperties ??= new())[LoopExitReason.AdditionalPropertiesKey] = LoopExitReason.TimeBudgetExceeded; | ||
| return budgetResult; |
Comment on lines
+643
to
+646
| elapsed = time.monotonic() - budget_start | ||
| if self.budget.max_duration is not None and elapsed >= self.budget.max_duration: | ||
| budget_exit = "time_budget_exceeded" | ||
| break |
Comment on lines
+173
to
+174
| # Fall back to additional_properties for framework-set reasons (e.g. iteration cap). | ||
| return response.additional_properties.get(LOOP_EXIT_REASON_KEY) or None |
Comment on lines
+10
to
+11
| ### Added | ||
| - **agent-framework-core**: Add `AgentBudget` with `max_tokens` and `max_duration` constraints for `AgentLoopMiddleware` autonomous loops; budget stops stamp `loop_exit_reason` in `AgentResponse.additional_properties`. Add `LoopExitReason.token_budget_exceeded` and `LoopExitReason.time_budget_exceeded`. |
Comment on lines
449
to
+450
| "Agent", | ||
| "AgentBudget", |
Adds token and wall-clock time budget constraints to AgentLoopMiddleware (Python) and LoopAgent (.NET), enabling safe production autonomous loops. AgentBudget (Python) / MaxTokens + MaxDuration (LoopAgentOptions, .NET): - max_tokens / MaxTokens: stops the loop when cumulative LLM token spend exceeds the limit; counts total_token_count per iteration - max_duration / MaxDuration: stops the loop when elapsed wall-clock time exceeds the limit; catches agents blocked on slow tools Budget checks run before should_continue / LoopEvaluators so an expensive judge predicate is not called after a budget is already exhausted. New LoopExitReason values (both languages): token_budget_exceeded -- token cap fired time_budget_exceeded -- wall-clock cap fired Exit reason stamped in AgentResponse.AdditionalProperties["loop_exit_reason"] on non-streaming runs. Streaming limitation documented as with the iteration cap. Depends on microsoft#8993 (GT1 signal protocol) which introduces LoopExitReason. Tests: 124 Python (7 new budget tests), .NET 4 new LoopAgentBudgetTests. Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Mark Licata (marklicata)
force-pushed
the
agent-budget
branch
from
October 5, 2026 15:33
93676ee to
325a2fd
Compare
Mark Licata (marklicata)
temporarily deployed
to
github-app-auth
October 5, 2026 15:33 — with
GitHub Actions
Inactive
Mark Licata (marklicata)
marked this pull request as ready for review
October 5, 2026 15:33
Mark Licata (marklicata)
requested review from
SergeyMenshykh,
Tao Chen (TaoChenOSU),
Vincent Biret (baywet),
Eduard van Valkenburg (eavanvalkenburg),
Giles Odigwe (giles17),
Jose Alvarez (jpalvarezl),
Evan Mattson (moonbox3),
Peter Ibekwe (peibekwe),
Roger Barreto (rogerbarreto),
sophia-ramsey and
westey (westey-m)
as code owners
October 5, 2026 15:33
Mark Licata (marklicata)
temporarily deployed
to
github-app-auth
October 5, 2026 15:40 — with
GitHub Actions
Inactive
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mark Licata (marklicata)
temporarily deployed
to
github-app-auth
October 6, 2026 20:17 — with
GitHub Actions
Inactive
Mark Licata (marklicata)
had a problem deploying
to
integration
October 6, 2026 20:18 — with
GitHub Actions
Failure
Mark Licata (marklicata)
had a problem deploying
to
integration
October 6, 2026 20:18 — with
GitHub Actions
Failure
Remove signal-protocol pieces that belong to microsoft#8993 (Python _signals.py, its exports and tests; .NET Completed/NeedInput/Cancelled exit reasons and evaluator exit-reason propagation) and AsyncRequestBroker API entries that belong to microsoft#8995. Iteration-cap stamping is kept since the budget exit-reason contract and tests depend on it. Also fix CI: restore PublicAPI BOMs, drop redundant System. qualifiers (IDE0001/IDE0002), use the field keyword for validated options (IDE0032), remove pyright-redundant None checks, and use a valid positive MaxDuration in the time-budget test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mark Licata (marklicata)
temporarily deployed
to
github-app-auth
October 6, 2026 20:48 — with
GitHub Actions
Inactive
Mark Licata (marklicata)
temporarily deployed
to
github-app-auth
October 6, 2026 20:50 — with
GitHub Actions
Inactive
This was referenced Oct 7, 2026
Author
This branch was successfully deployed
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
Closes #8862
Adds
AgentBudgetso operators can bound autonomous agent loops by token spend andwall-clock time. Without explicit limits, a confused or looping agent can burn tokens
indefinitely or block a session open on a slow external tool — neither is safe for
production deployment.
Description & Review Guide
What are the major changes?
Python (
agent_framework):AgentBudgetdataclass —max_tokens: int | Noneandmax_duration: float | None; both optional, no defaults (operators must set them explicitly for autonomous sessions)AgentLoopMiddleware— newbudget: AgentBudget | Noneconstructor arg; budget checks run beforeshould_continueso an expensive judge is not called when a budget is exhaustedtotal_token_countper iteration fromUsageDetailstime.monotonic()at loop start, checked per iterationAgentResponse.additional_properties["loop_exit_reason"]:"token_budget_exceeded"or"time_budget_exceeded"for budget stops (non-streaming only; streaming limitation documented), and"iteration_cap_reached"whenmax_iterationsfires.NET (
Microsoft.Agents.AI):LoopAgentOptions.MaxTokens(long?) andMaxDuration(TimeSpan?) — same semantics as PythonLoopAgent(non-streaming path) stampsLoopExitReason.TokenBudgetExceeded/TimeBudgetExceededon budget stops andLoopExitReason.IterationCapReachedwhenMaxIterationsfiresLoopExitReason— new[Experimental]static class:AdditionalPropertiesKey,IterationCapReached,TokenBudgetExceeded,TimeBudgetExceeded(values match the Python strings)PublicAPI.Unshipped.txtupdated for all 5 target frameworksWhat is the impact?
No breaking changes.
AgentBudgetis opt-in; loops without it behave identically to today.Relationship to #8993: This PR is self-contained and can be reviewed and merged independently. It shares the
loop_exit_reasonkey, iteration-cap stamping, and (.NET) theLoopExitReasonclass with #8993 (Signal Protocol); whichever merges second will take a merge frommainthat unions the exit-reason constants.What do you want reviewers to focus on?
should_continuetotal_token_countfallback toinput + outputwhen total is absent (provider-dependent)AdditionalPropertiesin streaming modeRelated Issue
Closes #8862
Contribution Checklist