Store hosted workflow checkpoints and approval state in the persistent session home, and add a deployable resilience sample that proves recovery across container crashes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1674d491-af7d-4f9e-99aa-5a829db81591
- Switch agentserver wheel sources from GitHub raw URLs to find-links at
local long-running-agents-private-preview checkout; remove [tool.uv.sources]
URL entries from python/pyproject.toml
- Re-lock uv.lock for find-links path sources
- Guard tracker.close() in cancellation path to avoid ValueError when
the SDK state machine is in not_started state
- Fix test assertions to access _ContextAwareCheckpointStorage._inner.storage_path
instead of .storage_path (which is not exposed directly on the wrapper)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous README asked users to Ctrl+C the server at the right moment
while counting to 1000, which was unreliable and the model often refused
the prompt anyway.
demo.py orchestrates everything automatically:
1. Starts main.py as a subprocess
2. Sends a background request (returns in_progress immediately)
3. Kills the server after 2s (no timing pressure)
4. Restarts the server (recovery scanner fires)
5. Polls until the response completes and prints it
Uses a substantive prose prompt (explain how the internet works) that
reliably generates a long response without model refusals.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- 01_basic: restore to minimal server-only sample; remove client.py,
test_steering.py, and all steering/resilience content from README.
Add brief 'Next steps' links to 13 and 14.
- 13_steering: new sample demonstrating steerable_conversations=True.
main.py starts the server; client.py is an interactive two-terminal
demo that sends two concurrent turns and shows the queued/steered flow.
- 14_resilience: new sample demonstrating resilient_background=True.
main.py starts the server with crash recovery enabled. README walks
through testing crash recovery locally (kill the server mid-response,
restart, and poll the response to see it recover).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add client.py — a two-terminal steering demonstration. Run main.py in
one terminal and client.py in a second to see steering in action with a
real Foundry agent:
- Turn 1 streams the agent counting to 50
- 2 s later, turn 2 arrives on the same conversation with a different question
- Turn 2 is accepted with status=queued (not HTTP 409)
- Turn 1's handler is cancelled and emits response.completed with partial output
- Turn 2 runs and its answer is printed
client.py accepts an optional SERVER_URL argument so it can be pointed at
a deployed instance as well as localhost.
Also enable steerable_conversations=True in main.py and update README with
the two-terminal usage instructions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
resilient_background=True was previously auto-enabled when running in a
hosted Foundry environment. This is now opt-in: callers must pass it
explicitly via ResponsesServerOptions.
server = ResponsesHostServer(
agent,
options=ResponsesServerOptions(resilient_background=True),
)
Reasoning: auto-enabling crash recovery silently can cause unexpected
behaviour for agents with non-idempotent side effects. Requiring explicit
opt-in makes the behaviour transparent and intentional.
Updates:
- _responses.py: remove auto-enable logic and AgentConfig check
- test_responses.py: replace auto-enable tests with a single test
asserting resilient_background is never auto-enabled
- 01_basic/main.py: rewrite comment to describe opt-in pattern
- 01_basic/README.md: rewrite durability section as opt-in
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the mock slow agent with a real FoundryChatClient + Agent using
FOUNDRY_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME env vars,
consistent with the 01_basic sample. Loads .env via python-dotenv.
Asks the agent to count to 100 (produces a long streaming response),
then steers with a joke request after 2s.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix async generator handler signature (b8 compatibility):
_handle_response was async def returning AsyncIterable. The b8 SDK
passes the raw handler result directly to _intercept_checkpoints which
does 'async for raw in handler_iterator' — this fails on a coroutine
with TypeError (caught as B8 pre-creation error), silently producing
empty responses. Changed to async generator (using yield) so calling
it returns an AsyncGenerator immediately iterable without awaiting.
Update 01_basic sample (durable_background -> resilient_background):
main.py comments and README referenced the old durable_background field
name (renamed to resilient_background in b8). Updated both to use the
correct API and added steerable_conversations documentation.
Add steering integration test (test_steering.py):
Self-contained end-to-end test using Hypercorn on a loopback port and
concurrent asyncio tasks. Demonstrates the full steering flow:
turn 1 streams, turn 2 arrives while turn 1 is in_progress and gets
status=queued, turn 1 emits response.completed (cancellation terminal
fix), turn 2 runs and completes. Requires clean ~/.agentserver state
(no stale task files) on first run; uses unique UUID per test run.
Also add pre-existing sample exclusions to pyrightconfig.samples.json
for monty/tools samples not installed in the dev environment.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix resilient_background rename: ResponsesServerOptions no longer
accepts durable_background (renamed to resilient_background in b8).
The old name caused a silent TypeError that disabled crash recovery
in hosted environments.
- Add steerable_conversations parameter to ResponsesHostServer.__init__.
When True, concurrent turns on the same conversation chain are queued
rather than rejected with 409 conversation_locked. The flag is threaded
into ResponsesServerOptions and composed with resilient_background in
the auto-enable path for hosted environments.
- Fix streaming cancellation terminal: both _handle_inner_agent and
_handle_inner_workflow now emit response.completed before returning
when cancellation_signal fires. Per spec §10, the handler must emit
a terminal event; the framework overrides completed → cancelled when
context.client_cancelled=True (real client cancel), and leaves it as
completed for steering pressure (client_cancelled=False). Previously
returning without a terminal caused the framework to force failed,
which broke the steered-turn chain.
- Add comment in _handle_inner_workflow explaining the relationship
between previous_response_id and conversation_chain_id for steerable
turns (they resolve to the same key, so the existing restore logic
is already correct).
- Update tests: rename resilient_background tests, update cancellation
test to assert response.completed is emitted, add three new tests
for steerable_conversations threading and precedence rules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix: Ollama parallel tool calls collide on same call_id
* fix(ollama): use uuid4 for tool call IDs and support colons in tool names
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* docs: add env example files for durabletask samples
* docs: clarify env example values and comments
* docs: set default Redis URL in streaming sample env example
* fix: clear service_session_id in _agent_wrapper when propagate_session=True
When propagate_session=True, the child agent inherits the parent's
service_session_id. After the parent's first LLM call, MAF auto-populates
this from the Responses API conversation_id. The child sends it as
previous_response_id which the server rejects because the parent's
tool_call is still pending (400 error).
This fix saves and clears service_session_id before calling the child
agent and restores it in a finally block, preserving session.state
sharing while isolating the server-side conversation pointer.
Fixes#5874
* refactor: use child session copy instead of in-place mutation
Address Copilot review comments:
- Create a child AgentSession with shared state dict but isolated
service_session_id, avoiding race conditions under concurrent
asyncio.gather tool invocations.
- Update tests to verify child gets a separate session object and
that child-set service_session_id does not leak to parent.
* fix: update test_chat_agent_as_tool_propagate_session_true for child session isolation
The existing test asserted captured_session is parent_session, but since
we now create a separate child AgentSession (to avoid racing under concurrent
asyncio.gather), the child is a different object. Updated assertions to verify:
- child is NOT the parent object (isolation)
- child shares the same session_id and state dict (by reference)
- child's service_session_id is None (isolated)
* fix: add type narrowing asserts for captured_session
Add 'assert captured_session is not None' before attribute access to
satisfy mypy/pyright type checking on Optional values.
* Python: Fix test typing checks
---------
Co-authored-by: Benke Qu <bequ@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
* Python: emit participant tool calls in AG-UI workflows
Decisions:
- Pass function call, function result, and approval request content from streaming agent updates regardless of role.
- Preserve the assistant-role gate for text and reuse the shared AG-UI content emitters without dual custom-event emission.
Files changed:
- packages/ag-ui/agent_framework_ag_ui/_workflow_run.py
- packages/ag-ui/tests/ag_ui/test_workflow_run.py
Verification:
- uv run poe test -P ag-ui
- uv run poe pyright -P ag-ui
- uv run poe test-typing -P ag-ui
- uv run poe syntax -P ag-ui -C
Notes:
- Existing workflow golden scenarios do not exercise participant tool calls, so no snapshot changed.
- No blockers.
* Python: guard participant tool call duplication
Decisions:
- Assert the workflow stream emits one TOOL_CALL_START when a streamed call is also present in final conversation history.
- Keep production flow unchanged because latest-assistant final-response conversion prevents duplication.
Files changed:
- packages/ag-ui/tests/ag_ui/test_workflow_run.py
Verification:
- uv run pytest packages/ag-ui/tests/ag_ui/test_workflow_run.py -k 'participant_tool_call or repeat_tool_call' -q
- uv run poe test -P ag-ui
- uv run poe pyright -P ag-ui
- uv run poe test-typing -P ag-ui
- uv run poe syntax -P ag-ui -C
- git diff --check
Notes:
- No blockers; no call-id guard was required.
* Python: scope workflow tool content bypass to resumable tool calls
- Exclude approval request content from the role bypass. Workflow approvals
resume through request_info pending state, so an approval interrupt emitted
from streamed content would have no pending request to resume against.
- Admit mcp_server_tool_call and mcp_server_tool_result so provider-hosted MCP
tool calls from workflow participants emit standard tool call events.
- Add unit tests for MCP passthrough, approval exclusion, and mixed
text-plus-tool content in non-assistant updates.
* Bump Python package versions for 1.11.0 release
Bump the CHANGELOG-selected packages for the 1.11.0 release: core and the root package move to 1.11.0 for the new stable APIs, Foundry and OpenAI receive patch bumps, changed prerelease packages receive the 260709 stamp or next RC counter, and Monty joins the bump set for corrected published dependency metadata. No beta cohort bump was applied. Raise core floors conservatively on every package publishing this cycle and correct dependency floors exposed by lower-bound validation.
Copilot-Session: ee33d338-c1fc-4182-9106-0345ccf26b8e
* Fix Gemini streaming type suppression
Move the targeted Pyright suppression to the SDK contents argument, where the google-genai invariant content-list alias produces the compatibility diagnostic, and remove the now-unnecessary member suppression.
Copilot-Session: ee33d338-c1fc-4182-9106-0345ccf26b8e
* Raise Monty core dependency floor
Align Monty with the conservative release policy by requiring agent-framework-core 1.11.0 or later for the package version published in this cycle.
Copilot-Session: ee33d338-c1fc-4182-9106-0345ccf26b8e
The RESOURCE_INSTRUCTIONS example told the model to use
eferences/FAQ instead of
eferences/FAQ.md, contradicting the
actual exact-match resource lookup (which lists and matches names
including the extension). This caused read_skill_resource to fail with
'Resource not found'. Align the example with the .NET original.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: fix removed ChatAgent references in _clients.py docstrings
* docs: make _clients.py tool-support examples copy/paste-safe
Import Agent in each tool-support protocol docstring example so
copy/pasting no longer raises NameError, and define the shell
executor (LocalShellTool) in the SupportsShellTool example.
Addresses Copilot review feedback on #6924.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: wrap SupportsShellTool example in async function
`async with LocalShellTool()` is a SyntaxError at module level, so the
copy/pasted snippet must live inside an async function to be valid.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Sumesh Bharathi Ramasamy <sumesh@iconicair.io>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Bind policy-enforcement approvals to a single tool invocation
PolicyEnforcementFunctionMiddleware retained approved call_ids in a set
that was never cleared, so a reused call_id could re-authorize a later
or different tool call without a fresh approval. It also accepted an
approved response as long as the invocation metadata carried a pending
call_id, without checking the response id or embedded function_call.
Bind each approval to the exact invocation shown for review: call_id,
function name, arguments, the security label (integrity/confidentiality),
and the session. Validate that the approval response itself names the
pending request (its id and embedded function_call), and consume the
approval on first use. A reused call_id, a different function, changed
arguments, an escalated label, a different session, or a mismatched
approved response now all require a fresh approval. Adds regression tests
covering each of those cases plus legitimate re-approval.
* Require approval response identifiers to be present and match
Make the policy-enforcement approval-response check reject a response
that omits its id or embedded function_call.call_id: both must now be
present and equal to the pending call_id, closing a None-identifier
bypass. Adds a regression test.
* Disclose all policy violations in a single approval request
PolicyEnforcementFunctionMiddleware computed the approval decision once
and reused it across the integrity and confidentiality checks, so a call
that violated both policies produced an approval request describing only
the untrusted-context violation and then silently waved the undisclosed
confidentiality violation on replay.
Detect every applicable violation up front and surface them together in a
single approval request, so a granted approval waves only what it
disclosed. The binding (call_id, function, arguments, security label,
session) and consume-once behavior are unchanged. Adds a regression test
covering a combined untrusted-context and confidentiality violation.
* Bind policy approval to the disclosed violation set and fingerprint
A pending policy approval was bound to the call body, security label, and
session but not to the violations it disclosed. Because the violation set
depends on the tool's policy metadata (max_allowed_confidentiality,
accepts_untrusted), a replay could compute a different or larger set after
that metadata changed and execute it under the old approval even though the
user never reviewed that risk.
Record the canonical disclosed violation fingerprint (type plus reason) in
the pending record and require the replay to trip the same set, otherwise
re-request approval disclosing the new set. Also require the approval
response's approved flag to be a strict boolean True so a truthy non-boolean
value is not treated as approval. Adds regression tests for a new violation
appearing on replay, a same-type violation whose disclosed risk worsened,
and a non-boolean approved flag.