.NET: Add BackgroundAgentsProvider.ReleaseSessionAsync to cancel and release per-session background tasks - #7602
Conversation
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
Motivation & Context
BackgroundAgentsProviderstarts each background task withTask.Run(() => agent.RunAsync(input, subSession))— with noCancellationToken. 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 inAgentSession.StateBagviaProviderSessionState<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?
BackgroundAgentsProvider:InvalidOperationExceptionwhencancelRunning: falseand tasks are still running, so background work is never silently orphaned.BackgroundAgentRuntimeStatenow tracks aCancellationTokenSourceper task (alongside a newIsReleasedflag), and the token is passed intoagent.RunAsync(...). A sharedStartTrackedRun/DisposeTaskCancellationhelper pair keeps start, continue, finalize, and clear paths consistent and avoids duplicating the lifecycle logic.timeout, withTimeout.InfiniteTimeSpansupported. A task that ignores its cancellation token is abandoned rather than wedging host eviction or shutdown.background_agents_start_taskandbackground_agents_continue_taskreturn an error string instead of starting new work, and any tasks stillRunningat release time are recorded asFailedwith an explanatory message, so a restored/serialized session does not report phantom running work.cancelRunning: falsepaths, terminal-status marking, tool behaviour after release, the timeout path, and argument validation.What is the impact of these changes?
CancellationToken, which is only ever signalled by an explicitReleaseSessionAsynccall.GetService<BackgroundAgentsProvider>(), so no new harness surface was needed.What do you want reviewers to focus on?
ReleaseSessionAsyncandWaitForTasksAsync, in particular that faults on abandoned tasks are observed so they never surface as unobserved task exceptions.Failedon release is the right terminal status, versus reusingLost.Related Issue
Fixes #7596
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.