Skip to content

Add token and wall-clock budget enforcement to AgentLoopMiddleware #8862

Description

Summary

AgentLoopMiddleware has one budget dimension today: max_iterations (default 10). This issue proposes adding token budget and wall-clock time budget as first-class parameters, integrated into the loop's existing stop evaluation, in both the Python and .NET implementations.

We intend to take on implementation.

User need

When a user delegates a task to an autonomous agent, they are accepting a cost in tokens and time. Two failure modes need to be bounded:

  1. Token runaway — an agent in a confused or stuck loop continues issuing LLM calls. Without a token cap, this continues until something external intervenes.
  2. Time runaway — an agent waiting on a slow or hung external system doesn't burn tokens, but holds a session open indefinitely. Token budgets alone don't catch this.

Operators deploying autonomous agents need explicit, enforced limits — not defaults that permit unbounded runs.

Developer need

There is no standard token or wall-clock budget primitive in MAF. Every team building autonomous loops implements cost controls from scratch, with different semantics for what happens when limits are hit, and no shared warning-threshold mechanism. Wall-clock limits in particular are rarely implemented correctly.

Additional gaps:

  • Hierarchical chargeback: when an agent spawns sub-agents via BackgroundAgentsProvider, sub-agent token spend is invisible to the parent's budget.
  • Warning thresholds: teams find out a budget was exceeded after the fact, not at 80% when the agent could still wrap up cleanly.

What's already there

  • Python: AgentLoopMiddleware (python/packages/core/agent_framework/_harness/_loop.py) — has max_iterations checked in _evaluate_stop before should_continue. This is the correct integration point for additional budget dimensions. ContextWindowCompactionStrategy handles context window tokens for compaction, not cost control.
  • .NET: AgentLoopMiddleware equivalent in dotnet/src/Microsoft.Agents.AI/Harness/Loop/ and dotnet/src/Microsoft.Agents.AI.Harness/. BackgroundAgentsProvider exists in both languages.
  • Neither implementation has max_tokens, max_duration_seconds, warning thresholds, or hierarchical chargeback.

Proposal

An AgentBudget type with three independently-configured dimensions, added as an optional parameter to AgentLoopMiddleware:

Dimension Field Default
Token budget max_tokens No default — operator-required
Iteration cap max_iterations Already exists
Wall-clock time max_duration_seconds No default — operator-required

max_tokens and max_duration_seconds have no framework defaults intentionally — they are too workload-specific. For autonomous sessions, the framework should require these to be set explicitly.

Integration into stop evaluation: budget checks run before should_continue, consistent with how max_iterations is already handled. Each dimension that fires produces a typed exit reason (see related issue #8860 on typed exit reasons).

Wall-clock enforcement: a background async task started when the loop begins. On expiry it cancels the loop.

Warning events at 80%: when any budget dimension reaches 80% of its limit, emit a warning into the agent context or session state so the agent can wrap up before the hard cap fires.

Hierarchical chargeback: child agent token spend (via BackgroundAgentsProvider) reduces the parent's remaining max_tokens. Wall-clock budget does not propagate — each agent in a hierarchy has its own time limit.

Scope

  • Both Python and .NET implementations are in scope. The Python harness (python/packages/core/agent_framework/_harness/) and .NET harness (dotnet/src/Microsoft.Agents.AI/Harness/ and dotnet/src/Microsoft.Agents.AI.Harness/) maintain feature parity; changes to one require equivalent changes in the other.
  • AgentBudget type: new in core (Python and .NET), experimental-flagged
  • Integration into AgentLoopMiddleware stop evaluation in both languages
  • Background async task for wall-clock enforcement
  • Chargeback hook into BackgroundAgentsProvider (may require design coordination)

Open questions

  1. Where should warning events surface — agent context injection, session state, a new event type, or all three?
  2. The chargeback integration with BackgroundAgentsProvider needs design review — specifically, whether chargeback should be opt-in or automatic when a budget is configured.

Related

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 29, 2026
  2. changed the title [-]Add token and wall-clock budget enforcement to AgentLoopMiddleware[/-] [+]Python: Add token and wall-clock budget enforcement to AgentLoopMiddleware[/+] on Sep 29, 2026
  3. changed the title [-]Python: Add token and wall-clock budget enforcement to AgentLoopMiddleware[/-] [+]Add token and wall-clock budget enforcement to AgentLoopMiddleware[/+] on Sep 29, 2026
  4. removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 29, 2026
  5. marklicata commented on Oct 1, 2026

    @marklicata
    Author

    Hello Eduard van Valkenburg (@eavanvalkenburg), would love to hear your thoughts on this work. And we would be taking on the implementation, so should only need review/oversight from the MAF team.

  6. eavanvalkenburg commented on Nov 2, 2026

    @eavanvalkenburg
    Member

    Mark Licata (@marklicata) The overall direction here makes sense to me. We can extend the existing loop types with budget limits. I would, however, keep sub-agent accounting out of the first framework implementation and demonstrate it in a sample instead.

    We plan to remove agent.as_tool in favor of samples (tracked in #8950) showing explicit agent-as-tool composition. There are several issues with that abstraction, including the accounting problems exposed here, so I do not want this work to deepen our dependency on it. This is not a request to remove the existing API in this PR.

    There is also a more fundamental accounting question: a sub-agent can use a different model from its parent. Adding their token counts does not necessarily describe the cost we want to limit. Different models have different input/output prices, and a local model might have no per-token charge at all. For a spend budget, those tokens need to become model-specific costs under an application-owned policy, rather than being treated as interchangeable tokens by the framework.

    For this first Python version, please:

    1. Keep the framework budget focused on the loop agent's own usage. Remove the automatic sub-agent chargeback additions, including the as_tool and background-agent rollups and their supporting telemetry changes. Document that child-agent usage is excluded from the built-in budget.
    2. Add a sample that exposes a child agent through a custom tool, calls the child explicitly, captures its usage, and demonstrates application-owned sub-agent accounting. Do not build that sample on agent.as_tool. Keep usage attributable to the relevant model and make the aggregation or cost policy explicit, so the example does not imply that tokens from different models always have the same cost.

    This gives us a useful first budget implementation and starts the sample-based replacement for agent.as_tool at the same time. It also lets applications choose the accounting policy that fits their models without committing core to a premature cost abstraction. The other feedback on loop-local duration enforcement and exit-reason handling still applies. We can revisit built-in hierarchical accounting once we have experience with the sample and a clearer contract for mixed-model costs.

  7. marklicata commented on Nov 3, 2026

    @marklicata
    Author

    Thanks Eduard van Valkenburg (@eavanvalkenburg). Updated commit pushed to #9164.

    Agreed and done:

    • Removed agent.as_tool rollup (as_tool is deprecated, Python: Remove Agent.as_tool() in vNext #8950)
    • Fixed max_duration: now wraps each iteration in asyncio.wait_for so in-flight calls are actually cancelled, not just skipped after the fact

    One point of disagreement on sub-agent accounting:
    We read max_tokens as a thinking budget — a bound on total inference work — not a cost proxy. An agent that routes around its reasoning cap by spawning background sub-agents has effectively bypassed the budget; rolling their tokens in is what makes the bound meaningful.

    If cost were the goal, max_tokens would already be the wrong abstraction regardless of rollup — you'd need model-specific pricing. I've updated the docstring to make the thinking-budget framing explicit. BackgroundAgentsProvider rollup stays.

    Happy to discuss if you see it differently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: Python

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions