Skip to content

.NET: Add BackgroundAgentsProvider.ReleaseSessionAsync to cancel and release per-session background tasks - #7602

Open
westey (westey-m) wants to merge 4 commits into
microsoft:mainfrom
westey-m:dotnet-add-background-task-releasing
Open

.NET: Add BackgroundAgentsProvider.ReleaseSessionAsync to cancel and release per-session background tasks#7602
westey (westey-m) wants to merge 4 commits into
microsoft:mainfrom
westey-m:dotnet-add-background-task-releasing

Conversation

@westey-m

Copy link
Copy Markdown
Contributor

Motivation & Context

BackgroundAgentsProvider starts each background task with Task.Run(() => agent.RunAsync(input, subSession)) — with no CancellationToken. There is no way for a host to signal "this session is over", so when a conversation ends or a host evicts a session, any background tasks that were still running keep executing: they continue invoking models and calling tools, producing results nobody will ever read, and any faults they raise go unobserved. In a long-lived host serving many concurrent sessions (for example a shared-agent web server), this is wasted compute and unwanted side effects for every abandoned conversation.

This is the .NET counterpart of the Python work in #7450 / #7385, adapted to the .NET design. Notably, the .NET provider does not have Python's unbounded dict[session_id, _RuntimeState] leak: runtime state is stored per-session in AgentSession.StateBag via ProviderSessionState<T>, so dropping the session already releases the memory. Porting Python's provider-level registry would actually introduce the very leak that fix removes, so this change focuses on the part that is genuinely missing in .NET — task lifecycle and cancellation.

Description & Review Guide

  • What are the major changes?

    • New public API on BackgroundAgentsProvider:
      public async Task ReleaseSessionAsync(
          AgentSession session,
          bool cancelRunning = true,
          TimeSpan? timeout = null,
          CancellationToken cancellationToken = default)
      It cancels and awaits all in-flight background tasks for the session, then disposes and clears the runtime references. It is idempotent, and throws InvalidOperationException when cancelRunning: false and tasks are still running, so background work is never silently orphaned.
    • Per-task cancellation plumbing: BackgroundAgentRuntimeState now tracks a CancellationTokenSource per task (alongside a new IsReleased flag), and the token is passed into agent.RunAsync(...). A shared StartTrackedRun / DisposeTaskCancellation helper pair keeps start, continue, finalize, and clear paths consistent and avoids duplicating the lifecycle logic.
    • The wait is bounded — 30 seconds by default, configurable via timeout, with Timeout.InfiniteTimeSpan supported. A task that ignores its cancellation token is abandoned rather than wedging host eviction or shutdown.
    • After release, background_agents_start_task and background_agents_continue_task return an error string instead of starting new work, and any tasks still Running at release time are recorded as Failed with an explanatory message, so a restored/serialized session does not report phantom running work.
    • Eight new unit tests covering cancellation and await semantics, idempotency, session isolation, both cancelRunning: false paths, terminal-status marking, tool behaviour after release, the timeout path, and argument validation.
  • What is the impact of these changes?

    • Hosts can deterministically tear down a session when a conversation ends, or from their own LRU/TTL eviction policy, and be confident that no background work outlives it.
    • The change is additive. The only behavioural difference for existing callers is that background runs now receive a real CancellationToken, which is only ever signalled by an explicit ReleaseSessionAsync call.
    • Hosts already have a handle to the provider via GetService<BackgroundAgentsProvider>(), so no new harness surface was needed.
  • What do you want reviewers to focus on?

    • The cancel/await/timeout semantics in ReleaseSessionAsync and WaitForTasksAsync, in particular that faults on abandoned tasks are observed so they never surface as unobserved task exceptions.
    • Whether marking still-running tasks as Failed on release is the right terminal status, versus reusing Lost.
    • The decision not to port Python's provider-level session registry, per the reasoning in Motivation & Context above.

Related Issue

Fixes #7596

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Copilot AI balanced review requested due to automatic review settings August 10, 2026 17:54
@westey-m
westey (westey-m) deployed to github-app-auth August 10, 2026 17:54 — with GitHub Actions Active
@westey-m
westey (westey-m) deployed to github-app-auth August 10, 2026 17:54 — with GitHub Actions Active
@westey-m
westey (westey-m) deployed to github-app-auth August 10, 2026 17:54 — with GitHub Actions Active
@agent-framework-automation agent-framework-automation Bot added the .NET Usage: [Issues, PRs], Target: .Net label Aug 10, 2026
@westey-m
westey (westey-m) marked this pull request as ready for review August 10, 2026 17:57
@westey-m
westey (westey-m) deployed to github-app-auth August 10, 2026 17:57 — with GitHub Actions Active

Copilot AI 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.

Pull request overview

Adds deterministic cleanup for per-session .NET background-agent tasks.

Changes:

  • Adds cancellation, bounded waiting, and release-state handling.
  • Tracks per-task cancellation sources.
  • Adds lifecycle and isolation tests.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
BackgroundAgentsProvider.cs Implements session release and task cancellation.
BackgroundAgentRuntimeState.cs Stores cancellation sources and release state.
BackgroundAgentsProviderTests.cs Tests release behavior and validation.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@github-actions github-actions 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.

Agent Framework Review — Iteration 1

Completed passes: 5 | Result: No high-severity findings

Scope: full PR (2 commit(s)): 32ace65c291f, 0bd46059c9ce

Review passes

  • Correctness (gpt-5.6-sol) — No issues found in this pass.
  • Security Reliability (claude-opus-4.8) — No issues found in this pass.
  • Test Coverage (gpt-5.6-sol) — No issues found in this pass.
  • Failure Modes (claude-opus-4.8) — No issues found in this pass.
  • Design Approach (claude-opus-4.8) — No issues found in this pass.

return ReleasedRuntimeStartError;
}

lock (runtimeState.SyncRoot)

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.

Task registration and child-session registration seem to happen under separate locks. Release can potentially clear the runtime between them, after which this code re-adds the session and reports that the task started. Would it make sense for all task-related references to be registered atomically under the same lock?


lock (runtimeState.SyncRoot)
{
if (runtimeState.IsReleased)

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.

If two callers release the session concurrently, the second returns before the first has finished waiting and cleaning up. Would that be an issue? If that's the case, maybe concurrent callers could await the release already in progress?

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

Labels

.NET Usage: [Issues, PRs], Target: .Net

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET: BackgroundAgentsProvider should provide a way to clear per-session runtime tasks

3 participants