Repository navigation
Conversation
TodoProvider kept its read-modify-write lock on the provider instance, so two providers over one store loaded the same state and next_id and one write was silently dropped while both calls reported success. The per-session lock was also not keyed by the event loop, so a session reused across asyncio.run boundaries could raise a loop-bound-lock RuntimeError. Move the lock to a module-level WeakValueDictionary keyed by store identity, session identity, and the running loop, mirroring the file-access store lock. Unrelated stores and sessions keep separate locks and are not serialized. Fixes microsoft#9868
Lushiyao5
requested review from
SergeyMenshykh,
Eduard van Valkenburg (eavanvalkenburg) and
Roger Barreto (rogerbarreto)
as code owners
October 31, 2026 22:25
Lushiyao5
temporarily deployed
to
github-app-auth
October 31, 2026 22:25 — with
GitHub Actions
Inactive
Lushiyao5
requested review from
Darren Cohen (dargilco),
Jose Alvarez (jpalvarezl),
sophia-ramsey and
westey (westey-m)
as code owners
October 31, 2026 22:25
Lushiyao5
had a problem deploying
to
github-app-auth
October 31, 2026 22:25 — with
GitHub Actions
Error
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Outstanding concurrency defects allow shared-file mutations to bypass locking or outlive it after cancellation, causing duplicate IDs and lost updates.
3 open issues
What changed in this PR
This PR addresses #9868 by moving Python todo mutation locks from individual providers to a shared registry, coordinating providers that use the same store and session object within an event loop.
Changes:
- Keys locks by store identity, running event loop, and session identity.
- Holds locks weakly so unused entries can be released.
- Adds regression coverage for shared-store mutations, lock cleanup, and event-loop isolation.
| File | Description |
|---|---|
python/packages/core/tests/core/test_harness_todo.py |
Tests shared-provider serialization, lock lifetime, and store/loop scoping. |
python/packages/core/agent_framework/_harness/_todo.py |
Replaces provider-owned locks with a shared weak-value lock registry. |
Review effort: Balanced
| The lock is process-local: other processes, and other stores over the same backing | ||
| storage, are not coordinated. | ||
| """ | ||
| key = (id(store), asyncio.get_running_loop(), id(session)) |
| The lock is process-local: other processes, and other stores over the same backing | ||
| storage, are not coordinated. | ||
| """ | ||
| key = (id(store), asyncio.get_running_loop(), id(session)) |
| self._mutation_locks[session] = lock | ||
| return lock | ||
| """Return the store-scoped lock for read-modify-write todo operations.""" | ||
| return _todo_mutation_lock(self.store, session) |
Author
|
@microsoft-github-policy-service agree |
… cancellation - Lock keys now come from the store's mutation identity instead of object ids, so two sessions resolving to the same todo file (or two stores over one directory) share a lock instead of overwriting each other. - Saves run under a shield, so a cancelled mutation keeps the lock until its in-flight write finishes and the cancellation is re-raised after.
Lushiyao5
temporarily deployed
to
github-app-auth
November 1, 2026 01:16 — with
GitHub Actions
Inactive
Author
|
Addressed the Copilot review comments (two fixed, one remains a documented limitation):
All 25 tests in |
This branch was previously 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
TodoProviderserializes its read-modify-write todo operations with a lock owned by the provider instance and keyed only by session. Two agents that each use their own provider over one sharedTodoStoretake different locks: both load the same state andnext_id, and the last save silently drops the other write while both tool calls report success for the same item id. A session reused acrossasyncio.runboundaries can also raise from a lock bound to the previous event loop.Description & Review Guide
(id(store), running loop, id(session))and held weakly, so providers sharing a store serialize with each other, unrelated sessions of one store still mutate concurrently, and a session is safe across event loops. Values live only while a mutation holds or awaits the lock.TodoProviderno longer owns aWeakKeyDictionaryof locks.Related Issue
Fixes #9868