agent-framework-core 1.20.0__tar.gz → 1.22.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/PKG-INFO +37 -30
  2. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/README.md +6 -1
  3. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/__init__.py +6 -0
  4. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/__init__.pyi +6 -0
  5. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_agent_hooks.py +2 -38
  6. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_agents.py +49 -6
  7. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_compaction.py +3 -5
  8. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_file_access.py +59 -16
  9. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_file_memory.py +9 -9
  10. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_memory.py +2 -2
  11. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_tool_approval.py +35 -4
  12. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_mcp.py +379 -101
  13. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_middleware.py +32 -0
  14. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_serialization.py +9 -3
  15. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_sessions.py +2 -1
  16. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_skills.py +5 -6
  17. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_tools.py +908 -180
  18. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_types.py +843 -127
  19. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_agent.py +34 -26
  20. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_agent_executor.py +1 -1
  21. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_edge.py +2 -1
  22. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_events.py +7 -5
  23. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_executor.py +7 -1
  24. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_functional.py +651 -53
  25. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_runner.py +45 -38
  26. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_typing_utils.py +23 -2
  27. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_viz.py +82 -33
  28. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_workflow.py +12 -5
  29. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_workflow_builder.py +5 -3
  30. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_workflow_executor.py +15 -0
  31. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/observability.py +73 -28
  32. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/security.py +316 -17
  33. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/pyproject.toml +30 -29
  34. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/LICENSE +0 -0
  35. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_clients.py +0 -0
  36. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_docstrings.py +0 -0
  37. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_evaluation.py +0 -0
  38. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_feature_stage.py +0 -0
  39. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_filesystem.py +0 -0
  40. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/__init__.py +0 -0
  41. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_agent.py +0 -0
  42. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_agent.pyi +0 -0
  43. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_background_agents.py +0 -0
  44. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_loop.py +0 -0
  45. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_mode.py +0 -0
  46. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_harness/_todo.py +0 -0
  47. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_in_memory.py +0 -0
  48. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_settings.py +0 -0
  49. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_telemetry.py +0 -0
  50. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_vector_filters.py +0 -0
  51. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_vectors.py +0 -0
  52. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/__init__.py +0 -0
  53. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_agent_utils.py +0 -0
  54. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_checkpoint.py +0 -0
  55. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_checkpoint_encoding.py +0 -0
  56. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_const.py +0 -0
  57. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_conversation_history.py +0 -0
  58. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_edge_runner.py +0 -0
  59. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_function_executor.py +0 -0
  60. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_message_utils.py +0 -0
  61. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_model_utils.py +0 -0
  62. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_request_info_mixin.py +0 -0
  63. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_runner_context.py +0 -0
  64. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_state.py +0 -0
  65. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_validation.py +0 -0
  66. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/_workflows/_workflow_context.py +0 -0
  67. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/a2a/__init__.py +0 -0
  68. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/a2a/__init__.pyi +0 -0
  69. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/ag_ui/__init__.py +0 -0
  70. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/ag_ui/__init__.pyi +0 -0
  71. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/amazon/__init__.py +0 -0
  72. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/amazon/__init__.pyi +0 -0
  73. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/anthropic/__init__.py +0 -0
  74. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/anthropic/__init__.pyi +0 -0
  75. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/azure/__init__.py +0 -0
  76. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/azure/__init__.pyi +0 -0
  77. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/chatkit/__init__.py +0 -0
  78. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/chatkit/__init__.pyi +0 -0
  79. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/declarative/__init__.py +0 -0
  80. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/declarative/__init__.pyi +0 -0
  81. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/devui/__init__.py +0 -0
  82. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/devui/__init__.pyi +0 -0
  83. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/exceptions.py +0 -0
  84. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/foundry/__init__.py +0 -0
  85. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/foundry/__init__.pyi +0 -0
  86. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/gemini/__init__.py +0 -0
  87. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/gemini/__init__.pyi +0 -0
  88. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/github/__init__.py +0 -0
  89. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/github/__init__.pyi +0 -0
  90. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/google/__init__.py +0 -0
  91. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/google/__init__.pyi +0 -0
  92. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/hyperlight/__init__.py +0 -0
  93. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/lab/__init__.py +0 -0
  94. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/mem0/__init__.py +0 -0
  95. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/mem0/__init__.pyi +0 -0
  96. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/microsoft/__init__.py +0 -0
  97. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/microsoft/__init__.pyi +0 -0
  98. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/mistral/__init__.py +0 -0
  99. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/mistral/__init__.pyi +0 -0
  100. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/monty/__init__.py +0 -0
  101. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/monty/__init__.pyi +0 -0
  102. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/ollama/__init__.py +0 -0
  103. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/ollama/__init__.pyi +0 -0
  104. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/openai/__init__.py +0 -0
  105. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/openai/__init__.pyi +0 -0
  106. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/orchestrations/__init__.py +0 -0
  107. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/orchestrations/__init__.pyi +0 -0
  108. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/py.typed +0 -0
  109. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/redis/__init__.py +0 -0
  110. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/redis/__init__.pyi +0 -0
  111. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/tools/__init__.py +0 -0
  112. {agent_framework_core-1.20.0 → agent_framework_core-1.22.0}/agent_framework/tools/__init__.pyi +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: agent-framework-core
3
- Version: 1.20.0
3
+ Version: 1.22.0
4
4
  Summary: Microsoft Agent Framework for building AI Agents with Python. This is the core package that has all the core abstractions and implementations.
5
5
  Author-email: Microsoft <af-support@microsoft.com>
6
6
  Requires-Python: >=3.10
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: 3.15
17
18
  Classifier: Typing :: Typed
18
19
  License-File: LICENSE
19
20
  Requires-Dist: msgspec>=0.20.0,<0.23
@@ -24,40 +25,41 @@ Requires-Dist: opentelemetry-api>=1.39.0,<2
24
25
  Requires-Dist: pyyaml>=6.0,<7.0
25
26
  Requires-Dist: regex>=2024.11.6
26
27
  Requires-Dist: mcp>=1.24.0,<2 ; extra == "all"
27
- Requires-Dist: agent-framework-a2a>=1.0.0b261002,<2 ; extra == "all"
28
- Requires-Dist: agent-framework-ag-ui>=1.5.0,<2 ; extra == "all"
29
- Requires-Dist: agent-framework-anthropic>=1.0.0b261002,<2 ; extra == "all"
30
- Requires-Dist: agent-framework-azure-ai-search>=1.0.0b261002,<2 ; extra == "all"
31
- Requires-Dist: agent-framework-azure-contentunderstanding>=1.0.0b261002,<2 ; extra == "all"
32
- Requires-Dist: agent-framework-azure-cosmos>=1.0.0b261002,<2 ; extra == "all"
28
+ Requires-Dist: agent-framework-a2a>=1.0.0b261015,<2 ; extra == "all"
29
+ Requires-Dist: agent-framework-ag-ui>=1.6.0,<2 ; extra == "all"
30
+ Requires-Dist: agent-framework-anthropic>=1.0.0b261015,<2 ; extra == "all"
31
+ Requires-Dist: agent-framework-azure-ai-search>=1.0.0b261015,<2 ; extra == "all"
32
+ Requires-Dist: agent-framework-azure-contentunderstanding>=1.0.0b261015,<2 ; extra == "all"
33
+ Requires-Dist: agent-framework-azure-cosmos>=1.0.0b261015,<2 ; extra == "all"
33
34
  Requires-Dist: agent-framework-azurefunctions>=1.0.0b260922,<2 ; extra == "all"
34
- Requires-Dist: agent-framework-bedrock>=1.0.0b261002,<2 ; extra == "all"
35
- Requires-Dist: agent-framework-chatkit>=1.0.0b261002,<2 ; extra == "all"
36
- Requires-Dist: agent-framework-claude>=1.0.0b261002,<2 ; extra == "all"
37
- Requires-Dist: agent-framework-copilotstudio>=1.0.0b261002,<2 ; extra == "all"
38
- Requires-Dist: agent-framework-declarative>=1.2.0,<2 ; extra == "all"
39
- Requires-Dist: agent-framework-devui>=1.0.0b261002,<2 ; extra == "all"
35
+ Requires-Dist: agent-framework-bedrock>=1.0.0b261015,<2 ; extra == "all"
36
+ Requires-Dist: agent-framework-chatkit>=1.0.0b261015,<2 ; extra == "all"
37
+ Requires-Dist: agent-framework-claude>=1.0.0b261015,<2 ; extra == "all"
38
+ Requires-Dist: agent-framework-copilotstudio>=1.0.0b261015,<2 ; extra == "all"
39
+ Requires-Dist: agent-framework-declarative>=1.3.0,<2 ; extra == "all"
40
+ Requires-Dist: agent-framework-devui>=1.0.0b261015,<2 ; extra == "all"
40
41
  Requires-Dist: agent-framework-durabletask>=1.0.0b260922,<2 ; extra == "all"
41
- Requires-Dist: agent-framework-foundry>=1.14.0,<2 ; extra == "all"
42
- Requires-Dist: agent-framework-foundry-hosting>=1.0.0b261002,<2 ; extra == "all"
43
- Requires-Dist: agent-framework-foundry-local>=1.0.0b260730,<2 ; extra == "all"
44
- Requires-Dist: agent-framework-gemini>=1.0.0b261002,<2 ; extra == "all"
45
- Requires-Dist: agent-framework-github-copilot>=2.0.1,<3 ; extra == "all" and ( python_version >= '3.11')
46
- Requires-Dist: agent-framework-hyperlight>=1.0.0b261002,<2 ; extra == "all" and ( python_version < '3.15' and ((sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and platform_machine == 'AMD64')))
47
- Requires-Dist: agent-framework-mem0>=1.0.0b261002,<2 ; extra == "all"
48
- Requires-Dist: agent-framework-mistral>=1.0.0b260918,<2 ; extra == "all"
49
- Requires-Dist: agent-framework-monty>=1.0.0b261002,<2 ; extra == "all"
50
- Requires-Dist: agent-framework-ollama>=1.0.0b261002,<2 ; extra == "all"
51
- Requires-Dist: agent-framework-openai>=1.15.0,<2 ; extra == "all"
52
- Requires-Dist: agent-framework-orchestrations>=1.3.0,<2 ; extra == "all"
53
- Requires-Dist: agent-framework-purview>=1.0.0b261002,<2 ; extra == "all"
54
- Requires-Dist: agent-framework-redis>=1.0.0b261002,<2 ; extra == "all" and ( python_version < '3.15')
55
- Requires-Dist: agent-framework-tools>=1.0.0b261002,<2 ; extra == "all"
42
+ Requires-Dist: agent-framework-foundry>=1.15.0,<2 ; extra == "all"
43
+ Requires-Dist: agent-framework-foundry-hosting>=1.0.0b261015,<2 ; extra == "all"
44
+ Requires-Dist: agent-framework-foundry-local>=1.0.0b261015,<2 ; extra == "all"
45
+ Requires-Dist: agent-framework-gemini>=1.0.0b261015,<2 ; extra == "all"
46
+ Requires-Dist: agent-framework-github-copilot>=2.1.0,<3 ; extra == "all" and ( python_version >= '3.11')
47
+ Requires-Dist: agent-framework-hyperlight>=1.0.0b261015,<2 ; extra == "all" and ( python_version < '3.15' and ((sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and platform_machine == 'AMD64')))
48
+ Requires-Dist: agent-framework-mem0>=1.0.0b261015,<2 ; extra == "all"
49
+ Requires-Dist: agent-framework-mistral>=1.0.0b261015,<2 ; extra == "all"
50
+ Requires-Dist: agent-framework-monty>=1.0.0b261015,<2 ; extra == "all"
51
+ Requires-Dist: agent-framework-ollama>=1.0.0b261015,<2 ; extra == "all"
52
+ Requires-Dist: agent-framework-openai>=1.17.0,<2 ; extra == "all"
53
+ Requires-Dist: agent-framework-orchestrations>=1.3.2,<2 ; extra == "all"
54
+ Requires-Dist: agent-framework-purview>=1.0.0b261015,<2 ; extra == "all"
55
+ Requires-Dist: agent-framework-redis>=1.0.0b261015,<2 ; extra == "all" and ( python_version < '3.15')
56
+ Requires-Dist: agent-framework-tools>=1.0.0b261015,<2 ; extra == "all"
56
57
  Project-URL: homepage, https://aka.ms/agent-framework
57
58
  Project-URL: issues, https://github.com/microsoft/agent-framework/issues
58
59
  Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
59
60
  Project-URL: source, https://github.com/microsoft/agent-framework/tree/main/python
60
61
  Provides-Extra: all
62
+ Import-Name: agent_framework
61
63
 
62
64
  # Get Started with Microsoft Agent Framework
63
65
 
@@ -83,9 +85,14 @@ pip install agent-framework-openai
83
85
 
84
86
  Supported Platforms:
85
87
 
86
- - Python: 3.10+
88
+ - Python: 3.10–3.15
87
89
  - OS: Windows, macOS, Linux
88
90
 
91
+ Python 3.10 remains supported and tested for this release, but it is deprecated
92
+ because upstream Python 3.10 has reached end of life. New environments should
93
+ use Python 3.11 or later. Removing Python 3.10 support will be handled in a
94
+ future release rather than by this compatibility update.
95
+
89
96
  ## 1. Setup API Keys
90
97
 
91
98
  Depending on the client you want to use, there are various environment variables you can set to configure the chat clients. This can be done in the environment itself, or with a `.env` file in your project root, some examples of environment variables include:
@@ -22,9 +22,14 @@ pip install agent-framework-openai
22
22
 
23
23
  Supported Platforms:
24
24
 
25
- - Python: 3.10+
25
+ - Python: 3.10–3.15
26
26
  - OS: Windows, macOS, Linux
27
27
 
28
+ Python 3.10 remains supported and tested for this release, but it is deprecated
29
+ because upstream Python 3.10 has reached end of life. New environments should
30
+ use Python 3.11 or later. Removing Python 3.10 support will be handled in a
31
+ future release rather than by this compatibility update.
32
+
28
33
  ## 1. Setup API Keys
29
34
 
30
35
  Depending on the client you want to use, there are various environment variables you can set to configure the chat clients. This can be done in the environment itself, or with a `.env` file in your project root, some examples of environment variables include:
@@ -277,12 +277,15 @@ _LAZY_MODULE_EXPORTS: Final[Mapping[str, tuple[str, ...]]] = {
277
277
  "UsageDetails",
278
278
  "add_usage_details",
279
279
  "detect_media_type_from_base64",
280
+ "get_namespaced_value",
280
281
  "map_chat_to_agent_update",
282
+ "merge_additional_properties",
281
283
  "merge_chat_options",
282
284
  "normalize_messages",
283
285
  "normalize_tools",
284
286
  "prepend_instructions_to_messages",
285
287
  "validate_chat_options",
288
+ "with_namespaced_value",
286
289
  "validate_tool_mode",
287
290
  "validate_tools",
288
291
  ),
@@ -696,6 +699,7 @@ __all__ = [
696
699
  "executor",
697
700
  "function_middleware",
698
701
  "get_agent_mode",
702
+ "get_namespaced_value",
699
703
  "get_run_context",
700
704
  "handler",
701
705
  "included_messages",
@@ -703,6 +707,7 @@ __all__ = [
703
707
  "keyword_check",
704
708
  "load_settings",
705
709
  "map_chat_to_agent_update",
710
+ "merge_additional_properties",
706
711
  "merge_chat_options",
707
712
  "normalize_function_invocation_configuration",
708
713
  "normalize_messages",
@@ -727,6 +732,7 @@ __all__ = [
727
732
  "validate_tools",
728
733
  "validate_workflow_graph",
729
734
  "vectorstoremodel",
735
+ "with_namespaced_value",
730
736
  "workflow",
731
737
  ]
732
738
 
@@ -243,7 +243,9 @@ from ._types import (
243
243
  UsageDetails,
244
244
  add_usage_details,
245
245
  detect_media_type_from_base64,
246
+ get_namespaced_value,
246
247
  map_chat_to_agent_update,
248
+ merge_additional_properties,
247
249
  merge_chat_options,
248
250
  normalize_messages,
249
251
  normalize_tools,
@@ -251,6 +253,7 @@ from ._types import (
251
253
  validate_chat_options,
252
254
  validate_tool_mode,
253
255
  validate_tools,
256
+ with_namespaced_value,
254
257
  )
255
258
  from ._vector_filters import Filter, FilterGroup, FilterGroupOperator, FilterOperator, Param
256
259
  from ._vectors import (
@@ -651,6 +654,7 @@ __all__ = [
651
654
  "executor",
652
655
  "function_middleware",
653
656
  "get_agent_mode",
657
+ "get_namespaced_value",
654
658
  "get_run_context",
655
659
  "handler",
656
660
  "included_messages",
@@ -658,6 +662,7 @@ __all__ = [
658
662
  "keyword_check",
659
663
  "load_settings",
660
664
  "map_chat_to_agent_update",
665
+ "merge_additional_properties",
661
666
  "merge_chat_options",
662
667
  "normalize_function_invocation_configuration",
663
668
  "normalize_messages",
@@ -682,5 +687,6 @@ __all__ = [
682
687
  "validate_tools",
683
688
  "validate_workflow_graph",
684
689
  "vectorstoremodel",
690
+ "with_namespaced_value",
685
691
  "workflow",
686
692
  ]
@@ -825,42 +825,6 @@ class _OutputCodec:
825
825
  # region Enforcement helpers
826
826
 
827
827
 
828
- def _chat_updates_from_response(response: ChatResponse[Any]) -> list[ChatResponseUpdate]:
829
- """Re-derive stream updates from a (transformed) assembled chat response."""
830
- updates = [
831
- ChatResponseUpdate(
832
- contents=list(message.contents),
833
- role=cast(Any, message.role),
834
- author_name=message.author_name,
835
- message_id=message.message_id,
836
- response_id=response.response_id,
837
- model=response.model,
838
- )
839
- for message in response.messages
840
- ]
841
- if not updates:
842
- updates = [ChatResponseUpdate(role="assistant", response_id=response.response_id)]
843
- updates[-1].finish_reason = response.finish_reason
844
- return updates
845
-
846
-
847
- def _agent_updates_from_response(response: AgentResponse[Any]) -> list[AgentResponseUpdate]:
848
- """Re-derive stream updates from a (transformed) assembled agent response."""
849
- updates = [
850
- AgentResponseUpdate(
851
- contents=list(message.contents),
852
- role=message.role,
853
- author_name=message.author_name,
854
- message_id=message.message_id,
855
- response_id=response.response_id,
856
- )
857
- for message in response.messages
858
- ]
859
- if not updates:
860
- updates = [AgentResponseUpdate(role="assistant", response_id=response.response_id)]
861
- return updates
862
-
863
-
864
828
  def _normalized_tools(tools: Any, *, point: str) -> list[Any]:
865
829
  """Normalize a tools value for projection; empty (with a warning) when it cannot be.
866
830
 
@@ -1313,7 +1277,7 @@ class _AgentHooksAgentMiddleware(_AgentHooksMiddlewareBase, AgentMiddleware):
1313
1277
  await self._emit_shutdown(state, "cancelled" if isinstance(exc, asyncio.CancelledError) else "error")
1314
1278
 
1315
1279
  context._stream_terminal_result_transforms.append(_transform) # pyright: ignore[reportPrivateUsage]
1316
- context._stream_terminal_result_to_updates = _agent_updates_from_response # pyright: ignore[reportPrivateUsage]
1280
+ context._stream_terminal_result_to_updates = AgentResponse.to_updates # pyright: ignore[reportPrivateUsage]
1317
1281
  context._stream_terminal_result_is_authoritative = True # pyright: ignore[reportPrivateUsage]
1318
1282
  context._stream_release_hooks.append(_release) # pyright: ignore[reportPrivateUsage]
1319
1283
  context._stream_release_error_hooks.append(_release_error) # pyright: ignore[reportPrivateUsage]
@@ -1443,7 +1407,7 @@ class _AgentHooksChatMiddleware(_AgentHooksMiddlewareBase, ChatMiddleware):
1443
1407
  gate_handle.drop()
1444
1408
 
1445
1409
  context._stream_terminal_result_transforms.append(_transform) # pyright: ignore[reportPrivateUsage]
1446
- context._stream_terminal_result_to_updates = _chat_updates_from_response # pyright: ignore[reportPrivateUsage]
1410
+ context._stream_terminal_result_to_updates = ChatResponse.to_updates # pyright: ignore[reportPrivateUsage]
1447
1411
  context._stream_terminal_result_is_authoritative = True # pyright: ignore[reportPrivateUsage]
1448
1412
  context._stream_release_hooks.append(_release) # pyright: ignore[reportPrivateUsage]
1449
1413
  context._stream_release_error_hooks.append(_release_error) # pyright: ignore[reportPrivateUsage]
@@ -81,6 +81,8 @@ else:
81
81
  from typing_extensions import Self, TypedDict # pragma: no cover
82
82
 
83
83
  if TYPE_CHECKING:
84
+ from collections.abc import Collection
85
+
84
86
  from mcp import types
85
87
  from mcp.server.lowlevel import Server
86
88
  from pydantic import BaseModel
@@ -113,6 +115,17 @@ def _tool_approval_source_ids(middleware: Sequence[MiddlewareTypes] | None) -> f
113
115
  return frozenset(item.source_id for item in middleware if isinstance(item, ToolApprovalMiddleware))
114
116
 
115
117
 
118
+ def _provider_service_session_state_keys(agent: object) -> frozenset[str]:
119
+ """Return provider-owned session-state keys declared by an agent or its client."""
120
+ keys: set[str] = set()
121
+ # A generic Agent can use a provider client that persists its own continuation state.
122
+ for owner in (agent, getattr(agent, "client", None)):
123
+ declared = getattr(owner, "service_session_state_keys", ())
124
+ if isinstance(declared, (list, tuple, set, frozenset)):
125
+ keys.update(key for key in cast("Collection[Any]", declared) if isinstance(key, str))
126
+ return frozenset(keys)
127
+
128
+
116
129
  def _merge_delegated_session_state(
117
130
  parent_state: MutableMapping[str, Any],
118
131
  initial_child_state: Mapping[str, Any],
@@ -665,9 +678,10 @@ class BaseAgent(SerializationMixin):
665
678
  propagate_session: If True, the parent agent's session is forwarded
666
679
  to this sub-agent's ``run()`` call. Application-state changes
667
680
  propagate back to the parent, while framework approval continuation
668
- state remains isolated. Defaults to False. The sub-agent always
669
- receives an AgentSession so session-backed middleware can run.
670
- When False, that session is private to this invocation.
681
+ state and provider-owned service session state remain isolated.
682
+ Defaults to False. The sub-agent always receives an AgentSession
683
+ so session-backed middleware can run. When False, that session is
684
+ private to this invocation.
671
685
 
672
686
  Returns:
673
687
  A FunctionTool that can be used as a tool by other agents.
@@ -683,6 +697,10 @@ class BaseAgent(SerializationMixin):
683
697
  values. The delegated call raises ToolExecutionException before running
684
698
  the child when their shared session-state keys overlap.
685
699
 
700
+ Direct or custom-loop invocation with ``propagate_session=True`` must set
701
+ :attr:`FunctionInvocationContext.parent_service_session_state_keys` when
702
+ the parent session is non-empty. Automatic function calling supplies it.
703
+
686
704
  Examples:
687
705
  .. code-block:: python
688
706
 
@@ -733,10 +751,24 @@ class BaseAgent(SerializationMixin):
733
751
  session = AgentSession()
734
752
  child_approval_source_ids = _tool_approval_source_ids(self.middleware)
735
753
  parent_approval_source_ids: frozenset[str] = frozenset()
754
+ parent_service_session_state_keys: frozenset[str] = frozenset()
736
755
 
737
756
  if propagate_session and parent_session is not None:
738
757
  from ._tools import _PARENT_TOOL_APPROVAL_SOURCE_IDS_CONTEXT_KEY # pyright: ignore[reportPrivateUsage]
739
758
 
759
+ # A custom loop can bypass the framework seam that identifies the parent provider's state.
760
+ # Refuse a non-empty shared session rather than guessing which keys are safe to delegate.
761
+ if ctx.parent_service_session_state_keys is None:
762
+ if parent_session.state:
763
+ raise ToolExecutionException(
764
+ f"Agent tool {tool_name!r} cannot safely propagate a non-empty parent session because "
765
+ "the invocation path did not provide parent provider-owned session state keys. "
766
+ "Use the automatic function-calling loop, set "
767
+ "FunctionInvocationContext.parent_service_session_state_keys in a custom loop, "
768
+ "or set propagate_session=False."
769
+ )
770
+ else:
771
+ parent_service_session_state_keys = ctx.parent_service_session_state_keys
740
772
  raw_parent_approval_source_ids = ctx.metadata.get(_PARENT_TOOL_APPROVAL_SOURCE_IDS_CONTEXT_KEY)
741
773
  parent_approval_source_ids = (
742
774
  cast("frozenset[str]", raw_parent_approval_source_ids)
@@ -770,6 +802,10 @@ class BaseAgent(SerializationMixin):
770
802
  _TOOL_APPROVAL_STATE_KEY,
771
803
  _FUNCTION_INVOCATION_BUDGET_STATE_KEY,
772
804
  _FUNCTION_RESULT_PAYLOAD_BUDGET_STATE_KEY,
805
+ # Service handles belong to one agent's remote resources, not shared application state.
806
+ # Exclude them in both directions so later children cannot inherit an earlier child's handles.
807
+ *_provider_service_session_state_keys(self),
808
+ *parent_service_session_state_keys,
773
809
  *child_approval_source_ids,
774
810
  *parent_approval_source_ids,
775
811
  })
@@ -959,7 +995,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
959
995
  tokenizer: TokenizerProtocol | None = None,
960
996
  additional_properties: MutableMapping[str, Any] | None = None,
961
997
  ) -> None:
962
- """Initialize a Agent instance.
998
+ """Initialize an Agent instance.
963
999
 
964
1000
  Args:
965
1001
  client: The chat client to use for the agent.
@@ -1589,7 +1625,10 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
1589
1625
 
1590
1626
  agent_name = self._get_agent_name()
1591
1627
  from ._mcp import MCPTool
1592
- from ._tools import _PARENT_TOOL_APPROVAL_SOURCE_IDS_CONTEXT_KEY # pyright: ignore[reportPrivateUsage]
1628
+ from ._tools import (
1629
+ _PARENT_SERVICE_SESSION_STATE_KEYS_CONTEXT_KEY, # pyright: ignore[reportPrivateUsage]
1630
+ _PARENT_TOOL_APPROVAL_SOURCE_IDS_CONTEXT_KEY, # pyright: ignore[reportPrivateUsage]
1631
+ )
1593
1632
 
1594
1633
  base_tools = _normalize_tools(chat_options.pop("tools", None))
1595
1634
  mcp_duplicate_message = "Tool names must be unique. Consider setting `tool_name_prefix` on the MCPTool."
@@ -1637,6 +1676,10 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
1637
1676
  additional_function_arguments[_PARENT_TOOL_APPROVAL_SOURCE_IDS_CONTEXT_KEY] = _tool_approval_source_ids(
1638
1677
  self.middleware
1639
1678
  )
1679
+ # Recompute ownership for this invoking agent; caller kwargs must not replace its declarations.
1680
+ additional_function_arguments[_PARENT_SERVICE_SESSION_STATE_KEYS_CONTEXT_KEY] = (
1681
+ _provider_service_session_state_keys(self)
1682
+ )
1640
1683
 
1641
1684
  model = opts.pop("model", None)
1642
1685
 
@@ -2061,7 +2104,7 @@ class Agent(
2061
2104
  tokenizer: TokenizerProtocol | None = None,
2062
2105
  additional_properties: MutableMapping[str, Any] | None = None,
2063
2106
  ) -> None:
2064
- """Initialize a Agent instance."""
2107
+ """Initialize an Agent instance."""
2065
2108
  super().__init__(
2066
2109
  client=client,
2067
2110
  instructions=instructions,
@@ -1013,11 +1013,9 @@ def _serialize_content(content: Content) -> dict[str, Any]:
1013
1013
  else:
1014
1014
  payload.pop("protected_data", None)
1015
1015
  additional_properties = payload.get("additional_properties")
1016
- if isinstance(additional_properties, dict) and "encrypted_content" in additional_properties:
1017
- typed_properties = cast("dict[str, Any]", additional_properties)
1018
- payload["additional_properties"] = {
1019
- key: value for key, value in typed_properties.items() if key != "encrypted_content"
1020
- }
1016
+ if isinstance(additional_properties, dict) and additional_properties:
1017
+ filtered_properties = _strip_opaque_reasoning_payload(additional_properties)
1018
+ payload["additional_properties"] = filtered_properties or {}
1021
1019
  return payload
1022
1020
 
1023
1021
 
@@ -28,6 +28,8 @@ import logging
28
28
  import os
29
29
  import threading
30
30
  import time
31
+ import unicodedata
32
+ import weakref
31
33
  from abc import ABC, abstractmethod
32
34
  from collections.abc import Awaitable, Mapping, MutableMapping
33
35
  from pathlib import Path
@@ -1718,6 +1720,52 @@ class _SearchFilesInput(BaseModel):
1718
1720
  ] = None
1719
1721
 
1720
1722
 
1723
+ # Locks handed out by ``_store_write_lock``. Values are held weakly, so an entry lasts only while an
1724
+ # edit holds or awaits its lock, and the map never grows with the number of paths ever edited.
1725
+ _STORE_WRITE_LOCKS: weakref.WeakValueDictionary[tuple[int, asyncio.AbstractEventLoop, str], asyncio.Lock] = (
1726
+ weakref.WeakValueDictionary()
1727
+ )
1728
+
1729
+
1730
+ def _store_write_lock(store: AgentFileStore, folder: str) -> asyncio.Lock:
1731
+ """Return the lock that serializes edits of the files directly inside ``folder`` in ``store``.
1732
+
1733
+ Mutating tools hold it across their read-modify-write. It belongs to the store
1734
+ instance rather than to one provider, so two providers sharing a store cannot both
1735
+ read a file before either writes it back and silently drop one of the edits.
1736
+ File-access tools lock the folder of the file they edit (see ``_parent_folder``) and
1737
+ file-memory tools lock their working folder, so the two kinds of provider also wait
1738
+ for each other when they edit the same file. Edits of different files in one folder
1739
+ therefore run one at a time.
1740
+
1741
+ The folder name is folded to NFC and lowercased because a store can treat such
1742
+ spellings as one folder: :class:`InMemoryAgentFileStore` ignores case, and macOS
1743
+ file systems resolve the NFC and NFD forms of a name to one entry. Elsewhere two
1744
+ spellings merely share a lock.
1745
+
1746
+ Locks are per event loop, because an :class:`asyncio.Lock` that has had a waiter on
1747
+ one loop raises when a caller has to wait for it on another. The key holds
1748
+ ``id(store)`` rather than the store, so a store need not be hashable (a default
1749
+ ``@dataclass`` is not) and is never kept alive by this map; the id cannot be reused
1750
+ while its lock is in use, because whoever holds or awaits the lock still references
1751
+ the store.
1752
+
1753
+ The lock is process-local: other processes, and other store instances over the same
1754
+ backing storage, are not coordinated.
1755
+ """
1756
+ key = (id(store), asyncio.get_running_loop(), unicodedata.normalize("NFC", folder).lower())
1757
+ lock = _STORE_WRITE_LOCKS.get(key)
1758
+ if lock is None:
1759
+ lock = asyncio.Lock()
1760
+ _STORE_WRITE_LOCKS[key] = lock
1761
+ return lock
1762
+
1763
+
1764
+ def _parent_folder(path: str) -> str:
1765
+ """Return the folder of a normalized store path (``""`` for a file at the root)."""
1766
+ return path.rpartition("/")[0]
1767
+
1768
+
1721
1769
  @experimental(feature_id=ExperimentalFeature.HARNESS)
1722
1770
  class FileAccessProvider(ContextProvider):
1723
1771
  """Context provider that gives an agent CRUD/search access to a shared file store.
@@ -1878,12 +1926,6 @@ class FileAccessProvider(ContextProvider):
1878
1926
  self.disable_write_tool_approval = disable_write_tool_approval
1879
1927
  self.session_scoped = session_scoped
1880
1928
  self.scope = scope
1881
- # Serializes mutating tool operations (write/delete/replace/replace_lines).
1882
- # The provider is shared across sessions/agents, so read-modify-write tools
1883
- # (replace/replace_lines) could otherwise interleave and lose updates. Note
1884
- # this only serializes within a single event loop/process, not across
1885
- # processes sharing a FileSystemAgentFileStore on disk.
1886
- self._write_lock = asyncio.Lock()
1887
1929
 
1888
1930
  def _resolve_session_key(self, context: SessionContext) -> str:
1889
1931
  """Resolve the working folder key for session-scoped mode.
@@ -1919,15 +1961,14 @@ class FileAccessProvider(ContextProvider):
1919
1961
  def _is_local_tool_call(function_call: Content) -> bool:
1920
1962
  """Return whether a function call targets this provider's local tools.
1921
1963
 
1922
- Hosted-tool calls carry a ``server_label`` in their
1923
- ``additional_properties`` and are a separate server-scoped approval
1964
+ Hosted-tool calls set ``hosted=True`` and are a separate server-scoped approval
1924
1965
  boundary that must be passed through untouched (see
1925
1966
  :func:`agent_framework._tools._is_hosted_tool_approval`). These rules
1926
1967
  only ever auto-approve the provider's own local tools, so any call that
1927
- carries a ``server_label`` is rejected even if its name collides with a
1968
+ is hosted is rejected even if its name collides with a
1928
1969
  file-access tool name.
1929
1970
  """
1930
- return not function_call.additional_properties.get("server_label")
1971
+ return not function_call.hosted
1931
1972
 
1932
1973
  @staticmethod
1933
1974
  def read_only_tools_auto_approval_rule(function_call: Content) -> bool:
@@ -1941,7 +1982,7 @@ class FileAccessProvider(ContextProvider):
1941
1982
  the tools that modify it (``file_access_write``, ``file_access_delete``,
1942
1983
  ``file_access_replace``, and ``file_access_replace_lines``).
1943
1984
 
1944
- Hosted-tool calls (those carrying a ``server_label``) are never
1985
+ Hosted-tool calls are never
1945
1986
  auto-approved, even when their name matches a file-access tool, so the
1946
1987
  rule stays scoped to this provider's local tools.
1947
1988
 
@@ -1978,7 +2019,7 @@ class FileAccessProvider(ContextProvider):
1978
2019
  ``file_access_delete``, ``file_access_replace``, and
1979
2020
  ``file_access_replace_lines``).
1980
2021
 
1981
- Hosted-tool calls (those carrying a ``server_label``) are never
2022
+ Hosted-tool calls are never
1982
2023
  auto-approved, even when their name matches a file-access tool, so the
1983
2024
  rule stays scoped to this provider's local tools.
1984
2025
 
@@ -2036,7 +2077,9 @@ class FileAccessProvider(ContextProvider):
2036
2077
  try:
2037
2078
  normalized = _normalize_relative_path(file_name)
2038
2079
  store_path = _session_path(normalized)
2039
- async with self._write_lock:
2080
+ # Every mutating tool takes the lock of the file's folder, which all providers
2081
+ # on the store share, so a write cannot land between another edit's read and write.
2082
+ async with _store_write_lock(self.store, _parent_folder(store_path)):
2040
2083
  await self.store.write(store_path, content, overwrite=overwrite)
2041
2084
  except NotADirectoryError:
2042
2085
  return f"Could not write file '{file_name}': a parent path is already a file. Choose a different path."
@@ -2091,7 +2134,7 @@ class FileAccessProvider(ContextProvider):
2091
2134
  try:
2092
2135
  normalized = _normalize_relative_path(file_name)
2093
2136
  store_path = _session_path(normalized)
2094
- async with self._write_lock:
2137
+ async with _store_write_lock(self.store, _parent_folder(store_path)):
2095
2138
  deleted = await self.store.delete(store_path)
2096
2139
  except ValueError as exc:
2097
2140
  return f"Could not delete file '{file_name}': {exc}"
@@ -2132,7 +2175,7 @@ class FileAccessProvider(ContextProvider):
2132
2175
  try:
2133
2176
  normalized = _normalize_relative_path(file_name)
2134
2177
  store_path = _session_path(normalized)
2135
- async with self._write_lock:
2178
+ async with _store_write_lock(self.store, _parent_folder(store_path)):
2136
2179
  content = await self.store.read(store_path)
2137
2180
  if content is None:
2138
2181
  return f"File '{file_name}' not found."
@@ -2154,7 +2197,7 @@ class FileAccessProvider(ContextProvider):
2154
2197
  try:
2155
2198
  normalized = _normalize_relative_path(file_name)
2156
2199
  store_path = _session_path(normalized)
2157
- async with self._write_lock:
2200
+ async with _store_write_lock(self.store, _parent_folder(store_path)):
2158
2201
  content = await self.store.read(store_path)
2159
2202
  if content is None:
2160
2203
  return f"File '{file_name}' not found."
@@ -42,7 +42,6 @@ per-invocation :class:`~agent_framework.SessionContext` in
42
42
 
43
43
  from __future__ import annotations
44
44
 
45
- import asyncio
46
45
  import logging
47
46
  from typing import Annotated, Any
48
47
 
@@ -60,6 +59,7 @@ from ._file_access import (
60
59
  _line_edits, # pyright: ignore[reportPrivateUsage]
61
60
  _matches_glob, # pyright: ignore[reportPrivateUsage]
62
61
  _normalize_relative_path, # pyright: ignore[reportPrivateUsage]
62
+ _store_write_lock, # pyright: ignore[reportPrivateUsage]
63
63
  )
64
64
 
65
65
  logger = logging.getLogger(__name__)
@@ -297,10 +297,6 @@ class FileMemoryProvider(ContextProvider):
297
297
  self.store = store
298
298
  self.scope = scope
299
299
  self.instructions = instructions or DEFAULT_FILE_MEMORY_INSTRUCTIONS
300
- # Serializes write/delete operations (and their index rebuilds) so the
301
- # ``memories.md`` index stays consistent. A single per-instance lock is
302
- # sufficient for v1; concurrent writes across scopes are rare in practice.
303
- self._write_lock = asyncio.Lock()
304
300
 
305
301
  def _resolve_working_folder(self, context: SessionContext) -> str:
306
302
  """Resolve the working folder for the current invocation.
@@ -382,7 +378,11 @@ class FileMemoryProvider(ContextProvider):
382
378
 
383
379
  path = _combine_paths(working_folder, normalized)
384
380
  desc_path = _combine_paths(working_folder, _description_file_name(normalized))
385
- async with self._write_lock:
381
+ # Every mutating tool locks the whole working folder, not just the file, because each
382
+ # write and delete rebuilds the folder's memories.md index. The lock is shared by all
383
+ # providers on the store, including file-access tools editing a file in this folder, so
384
+ # they cannot interleave edits or index rebuilds.
385
+ async with _store_write_lock(self.store, working_folder):
386
386
  try:
387
387
  await self.store.write(path, content)
388
388
  if description and description.strip():
@@ -427,7 +427,7 @@ class FileMemoryProvider(ContextProvider):
427
427
 
428
428
  path = _combine_paths(working_folder, normalized)
429
429
  desc_path = _combine_paths(working_folder, _description_file_name(normalized))
430
- async with self._write_lock:
430
+ async with _store_write_lock(self.store, working_folder):
431
431
  try:
432
432
  deleted = await self.store.delete(path)
433
433
  await self.store.delete(desc_path)
@@ -478,7 +478,7 @@ class FileMemoryProvider(ContextProvider):
478
478
  "Please choose a different file name."
479
479
  )
480
480
  path = _combine_paths(working_folder, normalized)
481
- async with self._write_lock:
481
+ async with _store_write_lock(self.store, working_folder):
482
482
  try:
483
483
  content = await self.store.read(path)
484
484
  if content is None:
@@ -506,7 +506,7 @@ class FileMemoryProvider(ContextProvider):
506
506
  "Please choose a different file name."
507
507
  )
508
508
  path = _combine_paths(working_folder, normalized)
509
- async with self._write_lock:
509
+ async with _store_write_lock(self.store, working_folder):
510
510
  try:
511
511
  content = await self.store.read(path)
512
512
  if content is None:
@@ -506,10 +506,10 @@ class MemoryTopicRecord:
506
506
  topic = stripped[2:].strip()
507
507
  current_section = None
508
508
  continue
509
- if stripped.startswith("Updated: "):
509
+ if current_section is None and stripped.startswith("Updated: "):
510
510
  updated_at = stripped.removeprefix("Updated: ").strip()
511
511
  continue
512
- if stripped.startswith("Sessions: "):
512
+ if current_section is None and stripped.startswith("Sessions: "):
513
513
  raw_sessions = stripped.removeprefix("Sessions: ").strip()
514
514
  session_ids = (
515
515
  []