agent-framework-core 1.15.0__tar.gz → 1.16.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 (109) hide show
  1. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/PKG-INFO +1 -1
  2. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_agents.py +3 -0
  3. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_agent.py +17 -3
  4. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_agent.pyi +2 -0
  5. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_background_agents.py +26 -3
  6. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_loop.py +4 -3
  7. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_serialization.py +2 -2
  8. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_types.py +5 -3
  9. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_agent.py +1 -5
  10. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_checkpoint.py +9 -8
  11. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_state.py +17 -10
  12. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/observability.py +406 -51
  13. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/pyproject.toml +1 -1
  14. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/LICENSE +0 -0
  15. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/README.md +0 -0
  16. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/__init__.py +0 -0
  17. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/__init__.pyi +0 -0
  18. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_agent_hooks.py +0 -0
  19. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_clients.py +0 -0
  20. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_compaction.py +0 -0
  21. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_docstrings.py +0 -0
  22. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_evaluation.py +0 -0
  23. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_feature_stage.py +0 -0
  24. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_filesystem.py +0 -0
  25. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/__init__.py +0 -0
  26. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_file_access.py +0 -0
  27. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_file_memory.py +0 -0
  28. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_memory.py +0 -0
  29. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_mode.py +0 -0
  30. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_todo.py +0 -0
  31. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_harness/_tool_approval.py +0 -0
  32. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_mcp.py +0 -0
  33. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_middleware.py +0 -0
  34. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_sessions.py +0 -0
  35. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_settings.py +0 -0
  36. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_skills.py +0 -0
  37. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_telemetry.py +0 -0
  38. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_tools.py +0 -0
  39. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/__init__.py +0 -0
  40. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_agent_executor.py +0 -0
  41. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_agent_utils.py +0 -0
  42. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_checkpoint_encoding.py +0 -0
  43. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_const.py +0 -0
  44. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_conversation_history.py +0 -0
  45. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_edge.py +0 -0
  46. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_edge_runner.py +0 -0
  47. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_events.py +0 -0
  48. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_executor.py +0 -0
  49. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_function_executor.py +0 -0
  50. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_functional.py +0 -0
  51. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_message_utils.py +0 -0
  52. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_model_utils.py +0 -0
  53. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_request_info_mixin.py +0 -0
  54. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_runner.py +0 -0
  55. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_runner_context.py +0 -0
  56. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_typing_utils.py +0 -0
  57. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_validation.py +0 -0
  58. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_viz.py +0 -0
  59. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_workflow.py +0 -0
  60. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_workflow_builder.py +0 -0
  61. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_workflow_context.py +0 -0
  62. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/_workflows/_workflow_executor.py +0 -0
  63. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/a2a/__init__.py +0 -0
  64. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/a2a/__init__.pyi +0 -0
  65. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/ag_ui/__init__.py +0 -0
  66. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/ag_ui/__init__.pyi +0 -0
  67. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/amazon/__init__.py +0 -0
  68. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/amazon/__init__.pyi +0 -0
  69. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/anthropic/__init__.py +0 -0
  70. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/anthropic/__init__.pyi +0 -0
  71. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/azure/__init__.py +0 -0
  72. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/azure/__init__.pyi +0 -0
  73. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/chatkit/__init__.py +0 -0
  74. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/chatkit/__init__.pyi +0 -0
  75. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/declarative/__init__.py +0 -0
  76. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/declarative/__init__.pyi +0 -0
  77. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/devui/__init__.py +0 -0
  78. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/devui/__init__.pyi +0 -0
  79. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/exceptions.py +0 -0
  80. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/foundry/__init__.py +0 -0
  81. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/foundry/__init__.pyi +0 -0
  82. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/gemini/__init__.py +0 -0
  83. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/gemini/__init__.pyi +0 -0
  84. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/github/__init__.py +0 -0
  85. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/github/__init__.pyi +0 -0
  86. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/google/__init__.py +0 -0
  87. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/google/__init__.pyi +0 -0
  88. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/hyperlight/__init__.py +0 -0
  89. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/lab/__init__.py +0 -0
  90. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/mem0/__init__.py +0 -0
  91. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/mem0/__init__.pyi +0 -0
  92. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/microsoft/__init__.py +0 -0
  93. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/microsoft/__init__.pyi +0 -0
  94. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/mistral/__init__.py +0 -0
  95. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/mistral/__init__.pyi +0 -0
  96. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/monty/__init__.py +0 -0
  97. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/monty/__init__.pyi +0 -0
  98. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/ollama/__init__.py +0 -0
  99. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/ollama/__init__.pyi +0 -0
  100. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/openai/__init__.py +0 -0
  101. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/openai/__init__.pyi +0 -0
  102. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/orchestrations/__init__.py +0 -0
  103. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/orchestrations/__init__.pyi +0 -0
  104. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/py.typed +0 -0
  105. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/redis/__init__.py +0 -0
  106. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/redis/__init__.pyi +0 -0
  107. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/security.py +0 -0
  108. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/tools/__init__.py +0 -0
  109. {agent_framework_core-1.15.0 → agent_framework_core-1.16.0}/agent_framework/tools/__init__.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-framework-core
3
- Version: 1.15.0
3
+ Version: 1.16.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
@@ -1512,6 +1512,9 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
1512
1512
  # _merge_options strips unset (None) options, so e.g. an unset `store` is not forwarded
1513
1513
  # and the service decides its own default.
1514
1514
  co = _merge_options(chat_options, run_opts)
1515
+ # The loop marker must remain on SessionContext.options for after_run provider
1516
+ # scoping, but it is framework-private metadata and must not reach the client.
1517
+ co.pop(_LOOP_ITERATION_TOKEN_KEY, None)
1515
1518
 
1516
1519
  # Build session_messages from session context: context messages + input messages
1517
1520
  session_messages: list[Message] = session_context.get_messages(include_input=True)
@@ -24,7 +24,7 @@ from .._sessions import ContextProvider, HistoryProvider, InMemoryHistoryProvide
24
24
  from .._skills import SkillsProvider
25
25
  from .._telemetry import FeatureIndex, mark_feature_used
26
26
  from .._types import ChatOptions
27
- from ._background_agents import BackgroundAgentsProvider
27
+ from ._background_agents import DEFAULT_BACKGROUND_AGENTS_WAIT_TIMEOUT_SECONDS, BackgroundAgentsProvider
28
28
  from ._file_access import AgentFileStore, FileAccessProvider, FileSystemAgentFileStore
29
29
  from ._file_memory import FileMemoryProvider
30
30
  from ._loop import DEFAULT_MAX_ITERATIONS, AgentLoopMiddleware
@@ -160,6 +160,7 @@ def _assemble_context_providers(
160
160
  skills_paths: str | Path | Sequence[str | Path] | None,
161
161
  background_agents: Sequence[SupportsAgentRun] | None,
162
162
  background_agents_instructions: str | None,
163
+ background_agents_wait_timeout_seconds: int,
163
164
  shell_context_provider: ContextProvider | None,
164
165
  extra_context_providers: Sequence[ContextProvider] | None,
165
166
  ) -> list[ContextProvider]:
@@ -205,7 +206,13 @@ def _assemble_context_providers(
205
206
 
206
207
  # Background agents are opt-in: only added when agents are provided.
207
208
  if background_agents:
208
- providers.append(BackgroundAgentsProvider(background_agents, instructions=background_agents_instructions))
209
+ providers.append(
210
+ BackgroundAgentsProvider(
211
+ background_agents,
212
+ instructions=background_agents_instructions,
213
+ wait_timeout_seconds=background_agents_wait_timeout_seconds,
214
+ )
215
+ )
209
216
 
210
217
  # Shell environment provider is opt-in: only added when a shell tool was wired.
211
218
  if shell_context_provider is not None:
@@ -329,6 +336,7 @@ def create_harness_agent(
329
336
  skills_paths: str | Path | Sequence[str | Path] | None = None,
330
337
  background_agents: Sequence[SupportsAgentRun] | None = None,
331
338
  background_agents_instructions: str | None = None,
339
+ background_agents_wait_timeout_seconds: int = DEFAULT_BACKGROUND_AGENTS_WAIT_TIMEOUT_SECONDS,
332
340
  shell_executor: ShellExecutor | None = None,
333
341
  shell_environment_provider_options: ShellEnvironmentProviderOptions | None = None,
334
342
  disable_web_search: bool = False,
@@ -478,6 +486,10 @@ def create_harness_agent(
478
486
  background_agents_instructions: Optional instruction override for the
479
487
  ``BackgroundAgentsProvider``. May include ``{background_agents}`` placeholder
480
488
  which will be replaced with the agent listing.
489
+ background_agents_wait_timeout_seconds: Maximum seconds the background-agent wait
490
+ tool blocks for a task to complete. Must be a positive integer.
491
+ Defaults to 300 seconds. On expiry, the tool returns normally and leaves the
492
+ background tasks running. Only used when ``background_agents`` is provided.
481
493
  shell_executor: Optional shell tool that enables shell command execution. When
482
494
  provided, the shell tool and a ``ShellEnvironmentProvider`` are automatically
483
495
  added (provided the client supports shell tools; otherwise a warning is logged
@@ -525,7 +537,8 @@ def create_harness_agent(
525
537
  Raises:
526
538
  ValueError: If max_context_window_tokens is provided and <= 0, or
527
539
  max_output_tokens is provided and <= 0, or max_output_tokens >=
528
- max_context_window_tokens when both are provided.
540
+ max_context_window_tokens when both are provided, or
541
+ background_agents_wait_timeout_seconds is invalid when background agents are provided.
529
542
  """
530
543
  if max_context_window_tokens is not None and max_context_window_tokens <= 0:
531
544
  raise ValueError("max_context_window_tokens must be positive.")
@@ -601,6 +614,7 @@ def create_harness_agent(
601
614
  skills_paths=skills_paths,
602
615
  background_agents=background_agents,
603
616
  background_agents_instructions=background_agents_instructions,
617
+ background_agents_wait_timeout_seconds=background_agents_wait_timeout_seconds,
604
618
  shell_context_provider=shell_provider,
605
619
  extra_context_providers=context_providers,
606
620
  )
@@ -14,6 +14,7 @@ from .._sessions import ContextProvider, HistoryProvider
14
14
  from .._skills import SkillsProvider
15
15
  from .._tools import ToolTypes
16
16
  from .._types import ChatOptions
17
+ from ._background_agents import DEFAULT_BACKGROUND_AGENTS_WAIT_TIMEOUT_SECONDS
17
18
  from ._file_access import AgentFileStore
18
19
  from ._loop import DEFAULT_MAX_ITERATIONS, NextMessageCallable, ShouldContinueCallable
19
20
  from ._mode import AgentModeProvider
@@ -77,6 +78,7 @@ def create_harness_agent(
77
78
  skills_paths: str | Path | Sequence[str | Path] | None = None,
78
79
  background_agents: Sequence[SupportsAgentRun] | None = None,
79
80
  background_agents_instructions: str | None = None,
81
+ background_agents_wait_timeout_seconds: int = DEFAULT_BACKGROUND_AGENTS_WAIT_TIMEOUT_SECONDS,
80
82
  shell_executor: _ShellExecutorLike | None = None,
81
83
  shell_environment_provider_options: _ShellEnvironmentProviderOptionsLike | None = None,
82
84
  disable_web_search: bool = False,
@@ -27,6 +27,7 @@ from .._types import AgentResponse, Message
27
27
  logger = logging.getLogger(__name__)
28
28
 
29
29
  DEFAULT_BACKGROUND_AGENTS_SOURCE_ID = "background_agents"
30
+ DEFAULT_BACKGROUND_AGENTS_WAIT_TIMEOUT_SECONDS = 300
30
31
 
31
32
  DEFAULT_BACKGROUND_AGENTS_INSTRUCTIONS = """\
32
33
  ## Background Agents
@@ -275,7 +276,8 @@ class BackgroundAgentsProvider(ContextProvider):
275
276
  This provider exposes the following tools to the agent:
276
277
 
277
278
  - ``background_agents_start_task`` — Start a background task on a named agent with text input.
278
- - ``background_agents_wait_for_first_completion`` — Block until the first of the specified tasks completes.
279
+ - ``background_agents_wait_for_first_completion`` — Wait until the first specified task completes or the
280
+ configured timeout expires. A timeout leaves the tasks running so the tool can be called again.
279
281
  - ``background_agents_get_task_results`` — Retrieve the text output of a completed background task.
280
282
  - ``background_agents_get_all_tasks`` — List all background tasks with their IDs, statuses, and descriptions.
281
283
  - ``background_agents_continue_task`` — Send follow-up input to a completed task's session to resume work.
@@ -297,6 +299,7 @@ class BackgroundAgentsProvider(ContextProvider):
297
299
  *,
298
300
  source_id: str = DEFAULT_BACKGROUND_AGENTS_SOURCE_ID,
299
301
  instructions: str | None = None,
302
+ wait_timeout_seconds: int = DEFAULT_BACKGROUND_AGENTS_WAIT_TIMEOUT_SECONDS,
300
303
  ) -> None:
301
304
  """Initialize the background agents provider.
302
305
 
@@ -312,13 +315,23 @@ class BackgroundAgentsProvider(ContextProvider):
312
315
  source_id: Unique source ID for serializable task state in session.
313
316
  instructions: Optional instruction override. May include ``{background_agents}``
314
317
  placeholder which will be replaced with the agent listing.
318
+ wait_timeout_seconds: Maximum seconds the wait tool blocks for a task to complete.
319
+ Must be a positive integer. Defaults to 300 seconds.
315
320
 
316
321
  Raises:
317
- ValueError: If agents is empty, an agent has no name, or names are not unique.
322
+ ValueError: If agents is empty, an agent has no name, names are not unique, or
323
+ wait_timeout_seconds is not a positive integer.
318
324
  """
319
325
  super().__init__(source_id)
320
326
 
321
327
  self._agents = _validate_and_build_agent_dict(agents)
328
+ if (
329
+ isinstance(wait_timeout_seconds, bool)
330
+ or not isinstance(wait_timeout_seconds, int)
331
+ or wait_timeout_seconds <= 0
332
+ ):
333
+ raise ValueError("wait_timeout_seconds must be a positive integer.")
334
+ self._wait_timeout_seconds = wait_timeout_seconds
322
335
 
323
336
  # Build instructions with agent listing.
324
337
  base_instructions = instructions if instructions is not None else DEFAULT_BACKGROUND_AGENTS_INSTRUCTIONS
@@ -502,7 +515,11 @@ class BackgroundAgentsProvider(ContextProvider):
502
515
 
503
516
  @tool(name="background_agents_wait_for_first_completion", approval_mode="never_require")
504
517
  async def background_agents_wait_for_first_completion(task_ids: list[int]) -> str:
505
- """Block until the first of the specified background tasks completes. Returns the completed task's ID."""
518
+ """Wait until the first of the specified tasks completes or the configured timeout expires.
519
+
520
+ Returns the completed task's ID. On timeout, the tasks remain running and this tool
521
+ can be called again to continue waiting.
522
+ """
506
523
  if runtime.closed:
507
524
  return "Error: Session is being released; cannot wait for background tasks."
508
525
 
@@ -531,8 +548,14 @@ class BackgroundAgentsProvider(ContextProvider):
531
548
  # Wait for the first one to complete.
532
549
  done, _ = await asyncio.wait(
533
550
  [t for _, t in waitable],
551
+ timeout=self._wait_timeout_seconds,
534
552
  return_when=asyncio.FIRST_COMPLETED,
535
553
  )
554
+ if not done:
555
+ return (
556
+ f"No background task completed within {self._wait_timeout_seconds} seconds. "
557
+ "The tasks are still running; call this tool again if you wish to continue waiting."
558
+ )
536
559
 
537
560
  # Find which ID completed.
538
561
  completed_id: int | None = None
@@ -34,7 +34,9 @@ from typing import TYPE_CHECKING, Any, TypeAlias
34
34
  from pydantic import BaseModel, Field
35
35
  from typing_extensions import Self
36
36
 
37
- from .._agents import _LOOP_ITERATION_TOKEN_KEY # pyright: ignore[reportPrivateUsage] -- shared loop-turn marker, see _agents.py
37
+ from .._agents import (
38
+ _LOOP_ITERATION_TOKEN_KEY, # pyright: ignore[reportPrivateUsage] -- shared loop-turn marker, see _agents.py
39
+ )
38
40
  from .._feature_stage import ExperimentalFeature, experimental
39
41
  from .._middleware import AgentContext, AgentMiddleware, MiddlewareTermination
40
42
  from .._sessions import SessionContext
@@ -490,8 +492,7 @@ class AgentLoopMiddleware(AgentMiddleware):
490
492
  if response is None or run_after is None:
491
493
  return
492
494
  if not any(
493
- getattr(provider, "after_run_once_per_turn", False)
494
- for provider in getattr(agent, "context_providers", [])
495
+ getattr(provider, "after_run_once_per_turn", False) for provider in getattr(agent, "context_providers", [])
495
496
  ):
496
497
  return
497
498
  session_context = SessionContext(
@@ -569,7 +569,7 @@ class SerializationMixin:
569
569
  and param_name in kwargs
570
570
  and isinstance(kwargs[param_name], dict)
571
571
  ):
572
- kwargs[param_name].update(param_value)
572
+ kwargs[param_name] = {**kwargs[param_name], **param_value}
573
573
  else:
574
574
  kwargs[param_name] = param_value
575
575
  else:
@@ -581,7 +581,7 @@ class SerializationMixin:
581
581
  )
582
582
  # Handle dict parameters - merge if both are dicts
583
583
  if isinstance(dep_value, dict) and dep_key in kwargs and isinstance(kwargs[dep_key], dict):
584
- kwargs[dep_key].update(dep_value)
584
+ kwargs[dep_key] = {**kwargs[dep_key], **dep_value}
585
585
  else:
586
586
  kwargs[dep_key] = dep_value
587
587
 
@@ -589,10 +589,10 @@ class Content:
589
589
  self.consent_link = consent_link
590
590
 
591
591
  def __deepcopy__(self, memo: dict[int, Any]) -> Content:
592
- """Create a deep copy, preserving ``_SHALLOW_COPY_FIELDS`` by reference.
592
+ """Create a deep copy, discarding non-``None`` ``_SHALLOW_COPY_FIELDS``.
593
593
 
594
594
  Fields listed in ``_SHALLOW_COPY_FIELDS`` may contain LLM SDK objects
595
- (e.g., proto/gRPC responses) that are not safe to deep-copy.
595
+ (e.g., proto/gRPC responses) that are not safe to deep-copy or share.
596
596
  """
597
597
  cls = type(self)
598
598
  result = cls.__new__(cls)
@@ -600,7 +600,9 @@ class Content:
600
600
  shallow = cls._SHALLOW_COPY_FIELDS
601
601
  for k, v in self.__dict__.items():
602
602
  if k in shallow:
603
- object.__setattr__(result, k, v)
603
+ if v is not None:
604
+ logger.debug("Discarding field '%s' while deep-copying Content.", k)
605
+ object.__setattr__(result, k, None)
604
606
  else:
605
607
  object.__setattr__(result, k, deepcopy(v, memo))
606
608
  return result
@@ -713,11 +713,7 @@ class WorkflowAgent(BaseAgent):
713
713
  Note:
714
714
  Text requests use the function-call envelope so callers can reply with a matching function result.
715
715
  """
716
- if (
717
- isinstance(event.data, Content)
718
- and event.data.user_input_request
719
- and event.data.type != "text"
720
- ):
716
+ if isinstance(event.data, Content) and event.data.user_input_request and event.data.type != "text":
721
717
  # Preserve specialized requests that callers already understand how to present.
722
718
  return event.data
723
719
 
@@ -130,7 +130,7 @@ class CheckpointStorage(Protocol):
130
130
  """Protocol for checkpoint storage backends."""
131
131
 
132
132
  async def save(self, checkpoint: WorkflowCheckpoint) -> CheckpointID:
133
- """Save a checkpoint and return its ID.
133
+ """Create a copy of the given checkpoint and store it, returning its ID.
134
134
 
135
135
  Args:
136
136
  checkpoint: The WorkflowCheckpoint object to save.
@@ -147,7 +147,7 @@ class CheckpointStorage(Protocol):
147
147
  checkpoint_id: The unique ID of the checkpoint to load.
148
148
 
149
149
  Returns:
150
- The WorkflowCheckpoint object corresponding to the given ID.
150
+ A copy of the WorkflowCheckpoint object corresponding to the given ID.
151
151
 
152
152
  Raises:
153
153
  WorkflowCheckpointException: If no checkpoint with the given ID exists.
@@ -161,7 +161,7 @@ class CheckpointStorage(Protocol):
161
161
  workflow_name: The name of the workflow to list checkpoints for.
162
162
 
163
163
  Returns:
164
- A list of WorkflowCheckpoint objects for the specified workflow name.
164
+ A list of copies of WorkflowCheckpoint objects for the specified workflow name.
165
165
  """
166
166
  ...
167
167
 
@@ -183,7 +183,8 @@ class CheckpointStorage(Protocol):
183
183
  workflow_name: The name of the workflow to get the latest checkpoint for.
184
184
 
185
185
  Returns:
186
- The latest WorkflowCheckpoint object for the specified workflow name, or None if no checkpoints exist.
186
+ A copy of the latest WorkflowCheckpoint object for the specified workflow name,
187
+ or None if no checkpoints exist.
187
188
  """
188
189
  ...
189
190
 
@@ -207,7 +208,7 @@ class InMemoryCheckpointStorage:
207
208
  self._checkpoints: dict[CheckpointID, WorkflowCheckpoint] = {}
208
209
 
209
210
  async def save(self, checkpoint: WorkflowCheckpoint) -> CheckpointID:
210
- """Save a checkpoint and return its ID."""
211
+ """Create a copy of the given checkpoint and store it, returning its ID."""
211
212
  self._checkpoints[checkpoint.checkpoint_id] = copy.deepcopy(checkpoint)
212
213
  logger.debug(f"Saved checkpoint {checkpoint.checkpoint_id} to memory")
213
214
  return checkpoint.checkpoint_id
@@ -217,12 +218,12 @@ class InMemoryCheckpointStorage:
217
218
  checkpoint = self._checkpoints.get(checkpoint_id)
218
219
  if checkpoint:
219
220
  logger.debug(f"Loaded checkpoint {checkpoint_id} from memory")
220
- return checkpoint
221
+ return copy.deepcopy(checkpoint)
221
222
  raise WorkflowCheckpointException(f"No checkpoint found with ID {checkpoint_id}")
222
223
 
223
224
  async def list_checkpoints(self, *, workflow_name: str) -> list[WorkflowCheckpoint]:
224
225
  """List checkpoint objects for a given workflow name."""
225
- return [cp for cp in self._checkpoints.values() if cp.workflow_name == workflow_name]
226
+ return [copy.deepcopy(cp) for cp in self._checkpoints.values() if cp.workflow_name == workflow_name]
226
227
 
227
228
  async def delete(self, checkpoint_id: CheckpointID) -> bool:
228
229
  """Delete a checkpoint by ID."""
@@ -239,7 +240,7 @@ class InMemoryCheckpointStorage:
239
240
  return None
240
241
  latest_checkpoint = max(checkpoints, key=lambda cp: datetime.fromisoformat(cp.timestamp))
241
242
  logger.debug(f"Latest checkpoint for workflow {workflow_name} is {latest_checkpoint.checkpoint_id}")
242
- return latest_checkpoint
243
+ return copy.deepcopy(latest_checkpoint)
243
244
 
244
245
  async def list_checkpoint_ids(self, *, workflow_name: str) -> list[CheckpointID]:
245
246
  """List checkpoint IDs. If workflow_id is provided, filter by that workflow."""
@@ -1,5 +1,6 @@
1
1
  # Copyright (c) Microsoft. All rights reserved.
2
2
 
3
+ import copy
3
4
  from typing import Any
4
5
 
5
6
 
@@ -31,7 +32,8 @@ class State:
31
32
  """Set a value in the pending state buffer.
32
33
 
33
34
  The value will be visible to subsequent `get()` calls but won't be
34
- committed to the actual state until `commit()` is called.
35
+ committed to the actual state until `commit()` is called. A deep copy is
36
+ stored so later caller mutations do not change pending state.
35
37
 
36
38
  Note:
37
39
  When multiple executors run concurrently within the same superstep,
@@ -40,7 +42,7 @@ class State:
40
42
  the .NET behavior and the superstep execution model where all executors
41
43
  in a superstep see the same committed state at the start.
42
44
  """
43
- self._pending[key] = value
45
+ self._pending[key] = copy.deepcopy(value)
44
46
 
45
47
  def get(self, key: str, default: Any = None) -> Any:
46
48
  """Get a value from state, checking pending first then committed.
@@ -50,14 +52,17 @@ class State:
50
52
  default: Value to return if key is not found. Defaults to None.
51
53
 
52
54
  Returns:
53
- The value if found, otherwise the default value.
55
+ A deep copy of the value if found, otherwise the default value. Mutate
56
+ the returned value and pass it to :meth:`set` to update workflow state.
54
57
  """
55
58
  if key in self._pending:
56
59
  value = self._pending[key]
57
60
  if value is _DeleteSentinel:
58
61
  return default
59
- return value
60
- return self._committed.get(key, default)
62
+ return copy.deepcopy(value)
63
+ if key in self._committed:
64
+ return copy.deepcopy(self._committed[key])
65
+ return default
61
66
 
62
67
  def has(self, key: str) -> bool:
63
68
  """Check if a key exists in pending or committed state."""
@@ -104,18 +109,20 @@ class State:
104
109
  self._pending.clear()
105
110
 
106
111
  def export_state(self) -> dict[str, Any]:
107
- """Export a serialized copy of the committed state.
112
+ """Export a deepcopy of the committed state.
108
113
 
109
- Note: Does not include pending changes.
114
+ Note:
115
+ Does not include pending changes. Values must support :func:`copy.deepcopy`.
110
116
  """
111
- return dict(self._committed)
117
+ return copy.deepcopy(self._committed)
112
118
 
113
119
  def import_state(self, state: dict[str, Any]) -> None:
114
120
  """Import state from a serialized dictionary.
115
121
 
116
- Merges into committed state. Does not affect pending changes.
122
+ Merges a deepcopy into committed state. Does not affect pending changes.
123
+ Values must support :func:`copy.deepcopy`.
117
124
  """
118
- self._committed.update(state)
125
+ self._committed.update(copy.deepcopy(state))
119
126
 
120
127
 
121
128
  class _DeleteSentinelType: