feat(agentserver): resilient task primitive + event streaming (core 2.0.0b7, invocations 1.0.0b6) - #46997
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Implements spec-009’s “pluggable stream handler” work for the durable task framework by introducing a StreamHandler protocol with a default QueueStreamHandler, plus related durable-task capabilities (retry, resume route, metadata, samples/tests) and extensive formatting/tidying across tests and samples.
Changes:
- Added a pluggable streaming abstraction (
StreamHandler,QueueStreamHandler, factory type) and wired it intoTaskContext.stream()andTaskRunasync iteration. - Introduced/expanded durable-task building blocks:
TaskResult,RetryPolicy, resume HTTP route, hosted provider client, lease renewal helper, and substantial new test coverage + samples. - Updated docs/changelogs and reformatted various tests/samples for style consistency.
Reviewed changes
Copilot reviewed 88 out of 92 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_tracing_e2e.py | Formatting-only adjustments (line wrapping/blank lines). |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_session_id.py | Formatting-only adjustments (blank lines, wrapped AsyncClient context). |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_server_routes.py | Formatting-only adjustments (blank lines). |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_request_limits.py | Minor whitespace cleanup. |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_request_id.py | Formatting-only adjustments. |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_multimodal_protocol.py | Minor whitespace cleanup and section spacing. |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_invoke.py | Formatting-only adjustments (blank lines). |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_graceful_shutdown.py | Formatting + wrapped long asserts for readability. |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_get_cancel.py | Minor whitespace cleanup. |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_edge_cases.py | Formatting-only adjustments (blank lines). |
| sdk/agentserver/azure-ai-agentserver-invocations/tests/test_decorator_pattern.py | Formatting (wrapped JSONResponse returns). |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/streaming_invoke_agent/streaming_invoke_agent.py | Reformatted token list for readability. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/simple_invoke_agent/simple_invoke_agent.py | Minor whitespace cleanup. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/multiturn_invoke_agent/multiturn_invoke_agent.py | Formatting; JSONResponse construction wrapped. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_multiturn/store.py | New sample persistence helper (file-backed JSON store). |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_multiturn/requirements.txt | New sample requirements. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_multiturn/app.py | New durable multiturn sample host wiring. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_multiturn/agent.py | New durable multiturn sample agent task. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_langgraph/store.py | New sample persistence helper (file-backed JSON store). |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_langgraph/requirements.txt | New sample requirements (LangGraph + deps). |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_langgraph/app.py | New streaming + steering durable LangGraph host sample. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_copilot/store.py | New sample persistence helper (file-backed JSON store). |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_copilot/requirements.txt | New sample requirements (Copilot SDK, core, Starlette, uvicorn). |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_copilot/app.py | New durable Copilot host sample with SSE. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_copilot/agent.py | New steerable durable Copilot agent sample. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_claude/store.py | New sample persistence helper (file-backed JSON store). |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_claude/requirements.txt | New sample requirements (Anthropic SDK + runtime deps). |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_claude/app.py | New durable Claude host sample with SSE. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_claude/agent.py | New steerable durable Claude agent sample. |
| sdk/agentserver/azure-ai-agentserver-invocations/samples/async_invoke_agent/async_invoke_agent.py | Formatting-only adjustments (wrapped JSON dict literals). |
| sdk/agentserver/azure-ai-agentserver-invocations/CHANGELOG.md | Changelog updates to mention durable samples + dependency bump. |
| sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py | Formatting-only adjustments. |
| sdk/agentserver/azure-ai-agentserver-core/tests/test_startup_logging.py | Formatting-only adjustments and wrapped long lines. |
| sdk/agentserver/azure-ai-agentserver-core/tests/test_server_routes.py | Minor whitespace cleanup. |
| sdk/agentserver/azure-ai-agentserver-core/tests/test_logger.py | Minor whitespace cleanup. |
| sdk/agentserver/azure-ai-agentserver-core/tests/test_graceful_shutdown.py | Formatting-only adjustments and wrapped long asserts. |
| sdk/agentserver/azure-ai-agentserver-core/tests/test_config.py | Formatting for long function signatures. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_task_result.py | New tests for TaskResult wrapper behavior + guardrails. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_streaming.py | New tests for pluggable stream handler integration. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_source.py | New tests exercising source field persistence. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_retry.py | New tests for RetryPolicy and retry integration. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_resume_route.py | New tests for the resume HTTP route behavior. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_models.py | New tests for durable models/exceptions. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_metadata.py | New tests for dict-like TaskMetadata + flush semantics. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_local_provider.py | New tests for local durable provider CRUD/listing. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_lifecycle.py | New lifecycle automation tests. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_get.py | New tests for DurableTask.get(). |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_entry_mode.py | New tests for ctx.entry_mode across paths. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_decorator.py | New tests for @durable_task decorator/options/type extraction. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_cancellation_timeout.py | New tests for cancellation, timeout, and termination. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_callable_factories.py | New tests for callable factories on tags/description. |
| sdk/agentserver/azure-ai-agentserver-core/tests/durable/init.py | New package init for durable tests. |
| sdk/agentserver/azure-ai-agentserver-core/tests/conftest.py | Formatting-only adjustments. |
| sdk/agentserver/azure-ai-agentserver-core/samples/durable_streaming/requirements.txt | New durable sample requirements. |
| sdk/agentserver/azure-ai-agentserver-core/samples/durable_streaming/durable_streaming.py | New sample demonstrating streaming with durable tasks. |
| sdk/agentserver/azure-ai-agentserver-core/samples/durable_source/requirements.txt | New durable sample requirements. |
| sdk/agentserver/azure-ai-agentserver-core/samples/durable_source/durable_source.py | New sample demonstrating source usage. |
| sdk/agentserver/azure-ai-agentserver-core/samples/durable_retry/requirements.txt | New durable sample requirements. |
| sdk/agentserver/azure-ai-agentserver-core/samples/durable_retry/durable_retry.py | New sample demonstrating retry policies. |
| sdk/agentserver/azure-ai-agentserver-core/pyproject.toml | Added httpx dependency + optional hosted extras (azure-identity). |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_stream.py | New StreamHandler protocol + default QueueStreamHandler + factory alias. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_run.py | New TaskRun async-iter streaming integration and lifecycle control methods. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_retry.py | New RetryPolicy implementation and presets. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_resume_route.py | New Starlette route for POST /tasks/resume. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_result.py | New TaskResult wrapper class. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_provider.py | New storage provider protocol for durable subsystem. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_metadata.py | New dict-like TaskMetadata with flush/auto-flush. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_lease.py | New lease identity utilities + renewal loop. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_exceptions.py | New durable exception types (failed/suspended/cancelled/etc.). |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_context.py | New TaskContext with stream support and lifecycle fields. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_client.py | New hosted durable task provider httpx client. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/init.py | New public durable API exports. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_middleware.py | Formatting-only adjustments for imports/log calls. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_errors.py | Minor formatting simplification. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_config.py | Minor formatting simplification. |
| sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/init.py | Minor whitespace cleanup. |
| sdk/agentserver/azure-ai-agentserver-core/README.md | Added durable-task documentation section + link. |
| sdk/agentserver/azure-ai-agentserver-core/CHANGELOG.md | Large changelog entry documenting durable subsystem and other changes. |
| sdk/agentserver/.gitignore | Added .vscode/ ignore. |
Comments suppressed due to low confidence (1)
sdk/agentserver/azure-ai-agentserver-invocations/samples/durable_multiturn/store.py:1
- For JSON persistence, it’s better to write/read with an explicit encoding (UTF-8) for cross-platform consistency. Consider using
open(fd, \"w\", encoding=\"utf-8\")(oros.fdopen) and also usingread_text(encoding=\"utf-8\")inload()to avoid platform-default encoding surprises.
Lands the RED tests for User Story 3 (public API cleanup) per FR-030 RED-first. T013 (test_decorator.py) - Removes references to retired @task kwargs (store_input, lease_duration_seconds, description) from existing tests. - Drops TaskOptions from the public import (demoted in __all__). - Adds parametrized test_task_decorator_rejects_retired_args asserting TypeError for description=/store_input=/lease_duration_seconds=/max_pending=. - Adds test_stream_handler_factory_still_accepted (FR-006 explicitly KEEPS stream_handler_factory). T014 (test_models.py) - Removes TaskSuspended import + test_task_suspended_reason. - Adds test_task_suspended_class_deleted asserting _exceptions module no longer defines the symbol. T015 (test_steering.py) - Removes ctx.previous_input reads from test_steered_context_fields. - Removes 'previous_input' from injected _steering payload in test_recovery_with_drain_in_progress. - Renames ctx.generation -> ctx.steering_generation in test asserts. - Adds test_task_context_previous_input_removed + new-name field tests. T016 (test_public_api_surface.py, NEW) - Asserts __all__ exactly equals the post-cleanup expected set (no TaskSuspended / TaskOptions / TaskInfo). - Asserts Task.get / Task.list are not public (renamed to _get/_list). T017 (test_entry_mode.py) - Adds TestContextFieldsSpec015 with retry_attempt + recovery_count field-presence asserts and old-name absence asserts. Verified RED: pytest -k rejects_retired_args or test_task_suspended_class_deleted or test_public_all_matches or test_retired_symbols or test_task_get_list -> 8 failed (decorator+models+public_api_surface) pytest -k test_task_context_previous_input_removed or test_task_context_steering_generation_field_present or test_task_context_retry_attempt_field_present or test_task_context_recovery_count_field_present -> 4 failed (entry_mode+steering) Total 12 new RED + 18 still-RED from contract_completeness meta-test. GREEN implementation lands in the next commit (T018-T027b). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the public-API cleanup user story (US3 / FR-006, FR-007, FR-008) on top of the Phase 3 RED tests committed in 9a8eedd. Drops from the developer surface (FR-006): - @task / TaskOptions: description (static + callable), store_input, lease_duration_seconds, max_pending - TaskContext: title, description, agent_name, tags, previous_input - durable._exceptions.TaskSuspended (class deleted entirely) - durable.__init__ __all__: TaskSuspended, TaskOptions, TaskInfo Renames on TaskContext (FR-007): - run_attempt -> retry_attempt - lease_generation -> recovery_count - generation -> steering_generation Demotes (FR-008): - TaskCallable.get / TaskCallable.list -> _get / _list (internal only) - TaskOptions -> internal (no longer in __all__) - TaskInfo -> internal (no longer in __all__) Storage changes: - payload["input"] is always written (store_input gate removed at TaskManager.create_and_start). Callers never opted out in practice. - _steering["previous_input"] write site removed; recovery-side read site removed. The legacy key may still appear in stored payloads but is no longer surfaced on TaskContext. Framework constants extracted (no developer knob): - durable._manager._DEFAULT_LEASE_SECONDS = 60 - durable._decorator._DEFAULT_MAX_PENDING_STEERING = 10 Cascading test/sample updates: - tests/durable: test_callable_factories, test_get, test_retry, test_sample_e2e, test_steering, test_stream_handler, test_streaming updated to use renamed fields and internal _get/_list. - samples/durable_retry: ctx.retry_attempt rename. - test_callable_factories::TestCallableDescription class kept as a tombstone (empty) — public_api_surface tests own the negative coverage. - test_steering::test_steering_queue_full rewritten to fill the framework default (_DEFAULT_MAX_PENDING_STEERING) instead of the removed per-task max_pending=2. - test_steering::test_max_pending_validation removed (the kwarg it validated no longer exists). Verification (Phase 3 exit criteria): - tests/durable/: 261 passed, 8 failed. All 8 failures are in test_contract_completeness.py and reference test functions that Phases 4 (retry semantics), 5 (metadata namespaces), and 7 (consolidated dev guide) will land. Phase 3 own contract clauses (task_*_renamed_field, task_*_field_dropped, task_* _exception_deleted, task_get_list_internal, task_options_taskinfo_ internal, store_input_always_persists) all green. - Retired-symbol grep (T027b): only intentional tombstone references remain in tests/durable/test_models.py, test_steering.py, test_entry_mode.py, test_public_api_surface.py, and test_contract_completeness.py. - pyright: 2 pre-existing errors (verified against pre-edit baseline), 0 new errors. - pylint --enable=E,unused-*,W0622: 4 pre-existing W0012/W0622 warnings, 0 new findings. Docs (durable-task-overview.md, durable-task-developer-guide.md, CHANGELOG.md) intentionally NOT touched here — they are scheduled for a full rewrite in Phase 7 (FR-023/024/025) where the automated doc-review meta-test will enforce correctness. Spec: 015-core-task-primitive-and-sample-fixups Branch: feature/agentserver-durable-tasks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the failing-by-design tests that pin the cross-lifetime contract
for ``ctx.retry_attempt`` and ``RetryPolicy.max_attempts``:
FR-001 ``ctx.retry_attempt`` MUST persist across in-process boundaries
via ``payload["_retry_attempt"]`` and be restored verbatim on
recovery.
FR-002 ``RetryPolicy.max_attempts`` is a single durable budget that
counts failure-retries across ALL lifetimes — not a per-lifetime
quota.
FR-003 Crash recovery MUST NOT consume any of the retry budget. Only
handler-raised exceptions consume it.
New tests in tests/durable/test_retry.py (TestRetryAttemptDurability):
- test_retry_attempt_cross_lifetime_durability
- test_retry_attempt_budget_exhausts_across_crash
- test_crash_recovery_does_not_consume_retry_budget
- test_steering_drain_resets_retry_attempt_to_zero
New tests in tests/durable/test_entry_mode.py (TestRecoveryRetryAttempt):
- test_recovered_handler_sees_persisted_retry_attempt
- test_recovery_entry_mode_does_not_increment_retry_attempt
All 6 tests fail RED today for the same root cause: the manager
hardcodes ``retry_attempt=0`` at every ``TaskContext`` construction
site, ignoring the payload-persisted counter. Phase 4 GREEN
(T030-T033) wires reads + writes + budget enforcement.
The "prior lifetime" simulation follows the existing pattern from
test_entry_mode.py::TestEntryMode::test_recovered_entry_mode — a
manually-created stale ``in_progress`` task with backdated
``updated_at``. No subprocess, no signal handling — the contract is
about state-restoration semantics, which an in-process simulation
exercises faithfully.
Contract enforcer pass: tests/durable/test_contract_completeness.py
now reports 5 failures (was 8). The 3 newly-paired clauses:
- retry_attempt_cross_lifetime_durability
- retry_budget_exhausts_across_crash
- crash_recovery_does_not_consume_retry_budget
Remaining 5 failures are all Phase 5 (metadata namespaces) and
Phase 7 (consolidated dev guide) work, as expected.
Branch: feature/agentserver-durable-tasks
Spec: 015-core-task-primitive-and-sample-fixups
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the cross-lifetime retry contract pinned by Phase 4 RED (0e3db4e): FR-001 ctx.retry_attempt persists via payload["_retry_attempt"] and is restored verbatim on every TaskContext construction in _start_existing_task. FR-002 RetryPolicy.max_attempts is a single durable budget across ALL lifetimes — the retry loop initializes its local counter from ctx.retry_attempt instead of resetting to 0. FR-003 Crash recovery does NOT consume the budget. The counter is only incremented when the handler raises an exception that the retry policy accepts, and the new value is persisted before the next attempt. Changes in _manager.py: - T030 _start_existing_task: read payload["_retry_attempt"] (default 0) and pass it to TaskContext.retry_attempt. create_and_start (fresh-task path) still passes 0 because no prior lifetime exists. - T031 _execute_task_loop initial state: ``attempt = ctx.retry_attempt`` so the loop honors the cross-lifetime counter. - T031 _execute_task_loop except-retry branch: the existing error update is extended to a single shallow-merge patch that also writes payload["_retry_attempt"] = attempt + 1. A subsequent crash + recover restores the bumped value via T030. - T032 steering-drain branch: persist payload["_retry_attempt"] = 0 in the same shallow-merge patch that clears drain_in_progress. Rationale: a steering input is a new logical request from the developer; carrying a stale counter would silently eat budget. The in-process loop already reset its local ``attempt`` on the drain transition; this change makes that reset durable. Changes in _retry.py: - T034 RetryPolicy.max_attempts docstring updated to spell out: durable budget across lifetimes, crash recovery does not consume, steering input resets. Verification: - tests/durable/ : 270 passed, 5 failed. - The 6 Phase 4 RED tests committed in 0e3db4e all turn GREEN on first run (no edits needed in the test files). - The remaining 5 failures are intentional Phase 5 (metadata namespaces, 4 tests) and Phase 7 (consolidated dev guide, 1 test). - test_contract_completeness.py: retry_attempt_cross_lifetime_durability PASS retry_budget_exhausts_across_crash PASS crash_recovery_does_not_consume_retry_budget PASS - pyright on changed files: 0 errors, 0 warnings. Branch: feature/agentserver-durable-tasks Spec: 015-core-task-primitive-and-sample-fixups Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 11 RED tests for TaskMetadata named-namespace facility: - test_default_namespace_callable_and_dict (FR-004) - test_named_namespace_isolation (FR-003) - test_named_namespace_auto_vivifies - test_namespaces_have_independent_dirty_tracking - test_flush_per_namespace_only (FR-003) [callback signature changes to (namespace, data)] - test_lifecycle_boundary_snapshots_all_touched_namespaces [new flush_all() API] - test_no_cross_namespace_pollution_after_delete - test_underscore_namespace_not_enforced_by_primitive (FR-005) - test_metadata_module_has_no_autoflush_symbols [source-scan: enforces T037 deletes start_auto_flush, stop_auto_flush, _auto_flush_loop, _flush_task, _flush_interval] - test_default_namespace_has_no_framework_keys - test_named_namespace_survives_recovery_with_independent_state [T036 in-process variant; real-crash subprocess harness deferred to Phase 8 deliverable] All 11 fail RED for the right reason: TaskMetadata is not callable yet; autoflush symbols still present. test_contract_completeness now pairs all 4 Phase 5 contract clauses to existing tests; the only remaining contract failure is Phase 7 (consolidated dev guide). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rewrite _metadata.py: TaskMetadata.__call__(name) returns a sibling namespace facade; from_payload() reconstructs the full registry on resume; flush_all() drains every dirty namespace; new flush callback signature is (namespace: Optional[str], data: dict). - Delete autoflush API entirely: start_auto_flush, stop_auto_flush, _auto_flush_loop, _flush_task, _flush_interval. Lifecycle boundaries in the manager now drive explicit flushes (FR-003). - _manager.py: resume path uses TaskMetadata.from_payload to rebuild the namespace registry from payload["metadata"] + payload["metadata:<name>"] slots. _make_metadata_flush persists per-namespace to the matching slot. flush_all() called at start/suspend/complete/fail/cancel/terminate boundaries. - _decorator.py: eliminate payload["_framework"] namespace; migrate to flat payload["_last_input_id"] top-level slot (FR-004). _* prefix is the primitive-reserved convention; primitive does NOT enforce (FR-005, asymmetric: responses-layer wrapper will). - 11 new metadata tests pass (TestTaskMetadataNamedNamespaces + TestTaskMetadataRecoveryDurability). 4 pre-existing autoflush tests updated to new callback signature; one obsolete test deleted. - Full durable suite: 284 pass / 1 fail (Phase 7 doc test, expected). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update existing tests for spec 015 FR-040 contract changes: - run_attempt → retry_attempt rename in all DurabilityContext fixtures - Delete test_task_stores_input (option removed in FR-006) - Add test_task_input_is_not_stored_via_decorator_option (asserts the store_input attr is gone from TaskOptions) - Rewrite test_metadata_hides_framework_keys → test_metadata_rejects_underscore_prefixed_keys (FR-005) - Add test_metadata_is_callable_for_named_namespace (FR-003) - Add test_named_namespace_also_rejects_underscore_prefix (FR-005 asymmetric enforcement on handler-facing wrapper) 22 tests fail RED — pending GREEN in T046-T056b. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rimitive surface - Rewrite _durability_context.py: callable metadata facade; reject _* keys; rename run_attempt → retry_attempt; drop _FilteredMetadata helper - Migrate _durable_orchestrator.py to ctx.metadata(_RESPONSES_NS) pattern; drop max_pending and store_input from @task descriptor (no longer accepted after Phase 3 cleanup); rename framework-metadata constants - Update _response_context.py to use retry_attempt= ctor kwarg - Migrate all responses-side test fixtures: callable metadata stub (_FakeTaskMetadata) replacing plain-dict fixture; drop _FilteredMetadata imports/wrappers; rename run_attempt → retry_attempt repo-wide Tests: - responses unit: 601 pass - responses e2e (non-live): 218 pass / 1 skipped - core durable: 284 pass / 1 fail (Phase 7 doc gate, expected) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…adapter - specs/durability-contract.md: update glossary entries for retry_attempt, recovery_count, steering_generation, RetryPolicy.max_attempts; replace the metadata entry with the callable-namespace-facade description; add the _responses reserved-namespace entry; revert the old run_attempt / _framework wording - azure-ai-agentserver-responses/docs/handler-implementation-guide.md and durable-responses-developer-guide.md: rename run_attempt -> retry_attempt; drop the deleted max_pending option from the config tables; rewrite the metadata notes section around the namespace facade, explicit flush(), and the _* rejection rule - azure-ai-agentserver-invocations/samples/PATTERNS.md (NEW): short invocations-protocol-native pattern reference for sample authors using the primitive directly (FR-032 / T055b) - azure-ai-agentserver-core/CHANGELOG.md + azure-ai-agentserver-responses/CHANGELOG.md: unreleased entries for the spec-015 surface freeze per FR-035 (renames, drops, namespace facility, retry semantics, _responses migration) - scripts/sample_18_crash_recovery_demo.py: opportunistic local script Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Six checks land RED until Phase 7 GREEN creates docs/durable-task-guide.md: - test_every_public_symbol_is_referenced_in_guide (FR-024 #1) - test_pr_blocking_failure_mode_for_undocumented_symbol (FR-025) - test_removed_names_absent_from_guide (FR-024 #2) - test_required_sections_present_in_order (FR-024 #3) - test_canonical_statements_present (FR-024 #4) - test_no_internal_contradictions (FR-024 #5) The T058 regression sub-test (test_pre_consolidation_state_would_have_failed) already passes — it proves the same checks bite when run against a synthetic pre-consolidation 'guide' that contains retired names, missing sections, and undocumented symbols. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… review NEW docs/durable-task-guide.md (~600 lines, 8-section learning arc): - §1 Why — the entrypoint-gap framing - §2 Mental Model — invariant, state-bucket diagram, entry-mode table - §3 Hello World — minimal example - §4 Concepts — retry semantics, callable namespace facade, suspend/ resume/steering, streaming, results, preconditions, exceptions - §5 Reference — full @task / Task / TaskContext / TaskMetadata / RetryPolicy / exception tables - §6 Patterns — 5 copy-and-ship idioms (at-most-once, watermark iteration, multi-turn, named namespaces, streaming) - §7 Operational / Testing — lifecycle boundary table, counters, local dev, crash-harness pointer - §8 What This Is NOT — explicit non-goals (replay, workflow engine, bulk store, queue) - Rename-map appendix (FR-007) Old guides converted to 10-line redirects to preserve incoming links. Meta-test test_dev_guide_review.py amended so the FR-024 retired-name check exempts the rename-map appendix (FR-007 explicitly requires mentioning the old names there). Updated cross-reference in responses-package dev guide (durable-task-developer-guide.md → durable-task-guide.md). Tests: 292 pass / 0 fail (was 291 pass / 1 fail under the Phase 7 RED). The doc-existence gate in test_contract_completeness.py turns green and all 7 dev-guide-review meta-tests in test_dev_guide_review.py pass. T057-T063 done. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… gate Adds tests/test_durable_samples_structure.py — 27 parametrized assertions covering FR-010 / FR-011 / FR-013 / FR-014 / SC-004: - Each of the 4 canonical durable samples (durable_copilot, durable_langgraph, durable_multiturn, durable_research) must exist and ship agent.py + app.py + README.md + requirements.txt. - durable_claude must be removed. - No sample's Python sources may reference Phase 3-6 retired names (ctx.generation, ctx.run_attempt, etc.). - durable_copilot/agent.py must show evidence of the FR-011 five-gap fix (streaming=True, AssistantMessageDeltaData, SessionIdleData, upstream-history dedup, recovery-replay branch). - durable-agent-demo must remain structurally intact (T073 promise). Land RED: 11 failed / 16 passed. The 11 failures are exactly the work items for Phase 8 GREEN (T068-T073). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ned task primitive
- T068 durable_copilot: rewrite to close 5 FR-011 gaps
* streaming=True wired into create_session + resume_session
* AssistantMessageDeltaData -> ctx.stream({type: text_delta}) chunks
* SessionIdleData -> ctx.stream({type: session_idle}) chunk
* Upstream-history dedup via session.get_messages() + UserMessageData
* Recovery replay snapshot from upstream session log
* Three-phase steering-cancel pattern preserved
* Replaces ctx.generation with ctx.steering_generation (FR-007)
- T069 durable_langgraph: verified no rewrite needed
Existing implementation already matches Phase 0 Q1 design:
one @task + LangGraph SqliteSaver/thread_id, NO DurabilityContext.
- T070 durable_multiturn: rewrite to use named-namespace metadata
* ctx.metadata (default) -> per-invocation state
* ctx.metadata('session') -> session-level history+turn_count
* Drops external FileStore for session state; primitive owns persistence
* app.py poll handler reads from session_workflow.get(task_id).payload
- T071 NEW durable_research: peer-sample-shape distillation of
durable-agent-demo/src/durable-research-agent
* 12-stage research loop with checkpoint-and-resume
* ctx.metadata watermark for completed_stages + results
* Live SSE streaming + async-poll modes
* Drops supervisor/entrypoint scaffolding of the reference demo
- T072 durable_claude: deleted (consumer-only design choice)
- T073 durable-agent-demo: verified untouched (no diff vs HEAD)
Structural gate: 23/27 pass; remaining 4 failures are missing
README.md files which will be created in Phase 9.
Refs: spec 015 FR-007 FR-010 FR-011 FR-012; Phase 0 Q1 Q2 Q2b.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… meta-test Adds tests/test_samples_shippable_bar.py as the static contract test for per-sample shippable bar (US6) and operational guidance (US7): - samples/SHIPPABLE.md exists and lists every required sample (FR-013). - samples/DURABLE_SAMPLES.md exists and links each per-sample README (FR-021). - Each sample/README.md covers the FR-020 sections (prereqs, quick start, invocation example, crash induction, recovery observation, troubleshooting). - Each sample/requirements.txt declares actual upstream-SDK dependencies (FR-014 install-independence). Run state (RED): 2 failed / 4 passed / 26 skipped. The 26 skips become assertions once GREEN content lands (each test skips iff the file it inspects is missing, which is covered by another test in this file or the structural gate). Refs: spec 015 FR-013 FR-014 FR-020 FR-021; US6, US7. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- T076-T079: per-sample README.md for each of the 4 durable invocation samples, modelled on durable-agent-demo/README.md but trimmed to peer-sample shape. Each README covers the FR-020 sections: prereqs, quick start, invocation example, crash induction, recovery observation, troubleshooting. - T080: samples/DURABLE_SAMPLES.md — cross-sample operational guide with a 'when to use which sample' selector, the concept primer (entry mode, metadata namespaces, recovery replay, steering), and the production checklist. - T082: samples/SHIPPABLE.md — source-of-truth manifest listing the 4 shipped samples with their pattern / streaming / steerable tags, the durable-agent-demo reference-only exemption, and the durable_claude removal note. - T083: per-sample requirements.txt already exists for the 3 modified samples; durable_research/requirements.txt landed in Phase 8. - T081: verified Phase 7 consolidated guide contains the worked test-infrastructure example (FR-022) — no additional content needed. - T075: regex tightened in test_samples_shippable_bar.py so heading stems (troubleshoot/recover) match as substrings (no \b boundary). Phase 9 gates green: 27/27 structural + 32/32 shippable-bar. Refs: spec 015 FR-013 FR-014 FR-020 FR-021 FR-022; US6, US7. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Core CHANGELOG: add Documentation section for new docs/durable-task-guide.md
consolidated guide + tests/durable/test_dev_guide_review.py CI doc-review
meta-test (Phase 7 T058). Update stale guide-path references ("durable-task-
developer-guide.md" -> "durable-task-guide.md") in the Features-Added and
Bugs-Fixed entries that pre-dated Phase 7. Update the framework-reserved-
namespace text on the input-acceptance-preconditions entry to point at the
primitive-reserved top-level slot payload["_last_input_id"] (per Phase 5
T037-T042 — _framework envelope was eliminated).
- Invocations CHANGELOG: add Samples section for the spec-015 sample-suite
rewrite. durable_copilot (5 FR-011 gaps closed; commit 59ad1f6),
durable_multiturn (named-namespace metadata), durable_langgraph (verified),
durable_research (new peer-sample), durable_claude (removed). New
samples/SHIPPABLE.md + samples/DURABLE_SAMPLES.md manifests + per-sample
READMEs. tests/test_samples_shippable_bar.py CI gate (32 assertions).
Phase 10 closeout artifacts (in gitignored specs/015 dir):
- post-mortem.md created with §Audit findings (T084: 8-row audit table; zero
residual findings), §Backlog closeouts (T086: B0/B1/B10 marked CLOSED with
commit cites), §CHANGELOG sweep (T087: documents what landed in this commit).
- backlog.md: B0 (auto-flush) marked CLOSED with Phase 5 cite; B1 (invocations
samples) marked CLOSED with Phase 8 commit 59ad1f6 cite; B10 (run_attempt
cross-lifetime) marked CLOSED with Phase 4 commit b9a3584 cite.
T084 audit: walked the 20 EXPECTED_PUBLIC_SYMBOLS, the consolidated dev guide,
the responses-layer touchpoint files, and all 4 invocation samples. No residual
gaps surfaced — all of FR-009 audit landed in Phases 3-9. T085: no new backlog
entries needed.
Branch: feature/agentserver-durable-tasks
Refs: spec 015 Phase 10 (US9 closeout); FR-009 (audit), FR-035 (changelog)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ification closeout T026 (close): the dev-guide already documents the public exception table (durable-task-guide.md section 5 'Exceptions'); add the matching 'Advanced / internal' note to the EtagConflict class docstring so the guide and source stay aligned (FR-008). T088-T094 (verification): three-suite verification per SC-009. - Responses (excl. live Copilot): 1306 pass / 1 skip (309s). - Responses durability conformance: 37 pass (178s). - Core durable + completeness: 382 pass / 5 skip / 2 pre-existing TestSigtermHandler failures from spec014 (verified at 2127bef). - Invocations e2e: 245 pass / 2 skip. - Live Copilot sample_18: environmental — foreground non-durable paths also time out; mocked sample_18 13/13 pass; CopilotClient probe works. Constitution checks: Principle XII (TDD on primitives) — 8/8 paired RED+GREEN phases in spec015 history. Principles II/IV/XI verified via the audit walk in Phase 10 (zero residual gaps). All 13 success criteria PASS (SC-009 with live-upstream caveat). Spec 015 — CLOSED. Branch: feature/agentserver-durable-tasks Spec: 015-core-task-primitive-and-sample-fixups FR: FR-008 (typed exceptions surfaced in guide) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The end-user-developer guide at docs/durable-task-guide.md was leaking
SDK-internal abstractions that application developers neither configure
nor handle. Fix this and lock the cleanup with the doc-review meta-test.
Changes to the guide:
* TaskStatus: now correctly documented as the four lifecycle states
from the foundry container spec (SOT): pending / in_progress /
suspended / completed. Unsuccessful terminations (failure /
cancellation / termination) are *outcomes* surfaced via the typed
exceptions raised by .run() / .result() — they are not separate
stored statuses. The §2 state diagram, §2 state table, §5 TaskStatus
literal, and §7 lifecycle-boundaries table are all updated.
* §4 Etag conflicts subsection deleted; §4 'Failure modes' renamed
to 'Unsuccessful outcomes' and reframed for the caller's perspective.
* §7 'Local development' rewritten: zero-configuration. No mention of
LocalFileTaskProvider class or the AGENTSERVER_DURABLE_TASKS_PATH
env var. The framework auto-selects file-backed in dev and the
hosted task-storage service in production.
* §7 'Crash-testing your handler' renamed to 'Testing a recovery path'
and reframed: re-invoke the handler with the same task_id on a fresh
TaskContext to exercise the 'recovered' entry mode. The SDK's own
_crash_harness is no longer referenced.
* §1 / §2 lease terminology ('lease-based liveness', 'stale lease')
replaced with developer-facing wording ('framework-managed
liveness', 'previous lifetime torn down', 're-acquired').
* §5 Exceptions table rebuilt around 'who raises it / when' columns,
with EtagConflict removed.
Public-surface change:
* EtagConflict is removed from durable/__init__.py.__all__. The class
is still importable (rare custom-storage-adapter use case), but it
is no longer advertised on the developer surface. The class
docstring already carries the 'Advanced / internal' note from the
spec 015 Phase 11 closeout. tests/durable/test_contract_completeness.py
and tests/durable/test_public_api_surface.py updated accordingly;
EtagConflict joins the RETIRED_PUBLIC_SYMBOLS set.
Meta-test extension:
* tests/durable/test_dev_guide_review.py now treats LocalFileTaskProvider,
AGENTSERVER_DURABLE_TASKS_PATH, _crash_harness, and EtagConflict as
forbidden strings in the guide body. The guide will fail CI if any
of these reappear.
CHANGELOG entry added documenting the cleanup.
Branch: feature/agentserver-durable-tasks
Spec: 015-core-task-primitive-and-sample-fixups (Phase 11 follow-up)
Source-of-truth cite: /tmp/foundrysdk_specs/specs/hosted-agents/container-spec/docs/durable-task-spec.md line 507 (TaskStatus literal)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-task-guide
Follow-up review of docs/durable-task-guide.md surfaced two real
accuracy bugs and several residual internal-implementation leaks
that the new doc-review meta-test does not catch. Fixing them.
Accuracy bugs
-------------
* §5 'Task (the handle)' previously documented the entry-point
signatures as:
.run(task_id, input, *, if_last_input_id=None, input_id=None)
.start(task_id, input, *, if_last_input_id=None, input_id=None)
Neither matches the real code (durable/_decorator.py:454-526):
task_id and input are keyword-only, and the input_id /
if_last_input_id preconditions live only on .start(), not on
.run(). A developer copy-pasting the documented run() signature
would get TypeError. Replaced with the actual signatures and an
explanatory sentence.
* §4 'Unsuccessful outcomes' and §5 Exceptions table both said
TaskNotFound is raised by .get() / .start(). There is no
Task.get() — .get() / .list() live on the (non-exported)
durable._client.Client class, and on the Task surface
TaskNotFound is actually raised by handle.result() (when the
referenced task_id has been deleted) and by .start() during
resume. Rewrote both places to cite the real raisers.
Residual internal leakage
-------------------------
* §4 metadata section leaked the on-disk payload schema
('the default namespace lives at payload["metadata"]; named
namespaces live at payload["metadata:<name>"]'). End-user
developers consume ctx.metadata(...); they never read the raw
payload. Deleted the sentence.
* §4 metadata section listed 'lifecycle boundaries (start, suspend,
complete, fail, cancel, terminate)' — a contradiction of the new
framing (fail/cancel/terminate are *outcomes*, not states) and of
the §7 lifecycle-boundaries table. Rewrote to '(task start,
ctx.suspend(...) return, handler return or unhandled raise)'.
* §5 Task section explicitly referenced 'internal .get() / .list()
helpers used by tests and the hosting layer; treat those as
not-for-end-user code'. If they are internal, the public guide
should not name them. Removed entirely.
* §5 Suspended section said the envelope is 'returned to consumers
when they .get() a task' — same .get() issue. Rewrote in terms of
the handler's return-await pattern and TaskResult.suspended.
* §7 Testing-recovery section referenced
'tests/durable/test_retry.py for a copy-paste cookbook example'.
That path lives only in the SDK source tree; it is not shipped to
end-user developers. Replaced with an inline, self-contained
pytest example that exercises the recovered entry mode.
* §5 RetryPolicy section showed
'RetryPolicy(max_attempts: int, backoff: ... = ...)' — ellipsis-
as-placeholder. Replaced with the actual constructor signature
from durable/_retry.py:52-62 and a short note on retry_on.
Clarity / consistency
---------------------
* §2 TaskStatus 'completed' row read as if cancellation / termination
*transition the task to* completed. Reframed to make the
status-vs-outcome split explicit: the status is completed; the
outcome (success / failure / cancellation / termination) is
surfaced through .run()/.result() per §4.
* §3 inline code comment for .run() was missing the 'or with a
task_id whose previous run already completed' qualifier that the
real _decorator.py:474-475 docstring carries; surrounding prose
did not mention the completed-collision case either. Both updated
to match the real lifecycle.
* §6 Pattern B note 'the metadata write is lifecycle-snapshotted'
used a term-of-art without forward reference; clarified that the
framework snapshots at lifecycle boundaries (defined in §4).
Verification
------------
* tests/durable/test_dev_guide_review.py: 7/7 pass
* tests/durable/test_public_api_surface.py: 12/12 pass
* tests/durable/test_contract_completeness.py: 11/11 pass
* full tests/durable/ suite: 292 pass / 0 fail / 1 unrelated warning
Branch: feature/agentserver-durable-tasks
Spec: 015-core-task-primitive-and-sample-fixups (Phase 11 follow-up)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…run/start Removes session_id, retry, and stream_handler from the public signatures of Task.run() and Task.start(). All three were footguns at the crash boundary: - session_id is platform-derived from FOUNDRY_AGENT_SESSION_ID; handler code has no business overriding it. - retry (RetryPolicy) and stream_handler are needed when crash recovery re-invokes the task on a fresh process. The manager's _recover_stale_tasks only sees the decorator-level opts (self._resume_opts.get(fn_name)), so any per-call override would silently disappear at recovery — the recovered lifetime would use the decorator config, not the per-call config. Removing them from the public surface forces the durable-safe shape: configure on @task (or Task.options) so the value survives across lifetimes. Also renames TaskContext.session_id to _session_id. It was framework-internal plumbing not intended for handler consumption; no external readers exist. Internal _lifecycle_start keeps the session_id/retry/stream_handler params because the manager threads platform-derived values through that boundary. Test changes (no semantic regressions): - tests/durable/test_stream_handler.py: 6 tests migrated from per-call stream_handler= to @task(stream_handler_factory=lambda _tid: handler). test_call_site_handler_overrides_factory deleted — it specifically validated the override behavior being removed. - tests/durable/test_retry.py: 5 tests migrated from per-call retry= to @task(retry=...). Verified: - core: 291/291 durable tests pass - responses: 1300/1301 non-live pass (one timing flake on test_shutdown_durable_background_not_marked_failed; passes in isolation) - responses durability_contract suite: 37/37 pass - invocations: 245/245 non-live pass CHANGELOG entry added under existing 2.0.0b4 section. Dev guide section 5 already updated in prior commit to reflect the trimmed signatures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…; add input_id parity on .run() Round 2 of the post-spec-015 API simplification on Task.run / Task.start per developer feedback on docs/durable-task-guide.md. * Drop `title`, `tags`, `stale_timeout` from `Task.run()` and `Task.start()`. All three are recovery-safe characteristics of the task identity and must survive crash/recovery; per-call overrides would silently disappear when recovery re-invokes the task on a fresh process. `title`/`tags` are configured on `@task(title=..., tags=...)` (both accept callable factories); `stale_timeout` is now a decorator option on `@task(stale_timeout=...)` and `Task.options(stale_timeout=...)`. Default unchanged at 300 seconds. * Add `input_id` and `if_last_input_id` to `Task.run()` (parity with `Task.start()`). Allows fire-and-await callers to use the optimistic- concurrency precondition without a separate .start() + .result() pair. * `TaskOptions` gains a `stale_timeout: float = 300.0` slot; propagated through `_lifecycle_start` from `self._opts`. * Doc updates in `durable-task-guide.md`: §5 `Task` signature trimmed to only the legitimately per-call kwargs; `@task` reference table enumerates every decorator option including the new `stale_timeout`; new §7 subsection 'Stale-task recovery and `stale_timeout`' explains what it does, what it interacts with (background lease-reclaim is separate), and when to override. * Delete the two legacy redirect docs (`durable-task-overview.md` and `durable-task-developer-guide.md`); update all in-tree references (README, contract-completeness error message, responses-orchestrator comment) to point at `durable-task-guide.md`. * Migrate 6 durable test files off the dropped per-call kwargs: promote to `@task(stale_timeout=...)` where the value is constant; use `Task.options(stale_timeout=...)` when a single test needs two variants on the same handler. Two tests covering the per-call `tags=` merge / reserved-stripping surface are deleted — the decorator-level coverage for both behaviours remains. Tests: - tests/durable/: 289 passed (down from 291; 2 tests covered a now- removed surface). - tests/durable/test_dev_guide_review.py + test_contract_completeness.py: 27 passed. - responses tests/e2e/durability_contract/: 37 passed. - invocations tests/: 245 passed. Pre-release packages (2.0.0b4); CHANGELOG appended in place. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # sdk/agentserver/azure-ai-agentserver-core/CHANGELOG.md # sdk/agentserver/azure-ai-agentserver-invocations/CHANGELOG.md # sdk/agentserver/azure-ai-agentserver-invocations/pyproject.toml # sdk/agentserver/azure-ai-agentserver-responses/CHANGELOG.md
Addresses Copilot review feedback: replace deprecated asyncio.get_event_loop() with asyncio.get_running_loop() in the resilient-task manager/lease code paths, all of which execute inside a running event loop. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5222ab61-7d5e-4642-b75f-ef365e375f4f
This comment has been minimized.
This comment has been minimized.
…ken tests Samples: - resilient_multiturn poll endpoint now reads per-invocation state from the nested payload["metadata"] slot (where the default metadata namespace is persisted) instead of the payload top level, which always returned 404. - resilient_research cancel handler now resolves the active run via the manager's task_id-only accessor instead of MultiTurnTask.get_active_run, which requires the in-flight turn's input_id and raised TypeError (500). Tests: - test_etag_cas: seed the stale task with schema_version so it is reclaimed (not legacy-deleted) and assert the reclaim issued a PATCH, so the if_match CAS assertion is no longer vacuous. - test_lease_renewal: shrink the lease so the renewal loop actually ticks within the test window, so the dynamic-cadence shadow assertion exercises the shadow logic instead of passing by construction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5222ab61-7d5e-4642-b75f-ef365e375f4f
Mirror the existing POSIX-only skip convention (not hasattr(os, "fork")) for tests that cannot run on Windows: - test_contract_completeness: module-level skip — its helpers scan repo source files with the platform default encoding, and cp1252 on Windows cannot decode UTF-8 source bytes (e.g. the 0x90 byte in test_lifecycle.py). - test_us4_support: skip only the grep-based regression guard, which shells out to the POSIX `grep` binary (absent on Windows). The other tests still run everywhere. - test_resilient_langgraph / test_resilient_research_live: skip on non-POSIX because CrashHarness drives crash recovery via POSIX process-group signals (os.killpg / os.getpgid / start_new_session). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5222ab61-7d5e-4642-b75f-ef365e375f4f
This comment has been minimized.
This comment has been minimized.
…d CI failures Pre-existing PR CI failures (not from the sample/test fixes): - Verify Links: docs/streaming-guide.md linked to `./tasks-guide.md` with a relative path; switched to the full GitHub URL (the link checker rewrites blob/main -> PR SHA), matching the CHANGELOG convention. - Build Analyze (cspell): added the technical words configurators, CREAT, impls, RDWR, unimportable to the core package cspell ignore list. - Build Docs (Sphinx): fixed six reST docstring warnings (warnings are errors) in _base.AgentServerHost.register_pre_shutdown_callback, streaming/_protocol.EventStreamNotFoundError, the tasks package docstring, tasks/_context.TaskContext.exit_for_recovery and tasks/_decorator.multi_turn_task (stray indents, an unterminated inline literal, an orphaned period, and an under-indented field continuation). Validated clean with docutils. - consistency: regenerated api.md / api.metadata.yml for azure-ai-agentserver-core with apiview-stub-generator 0.3.30 (the committed api.md rendered a stale, short @DataClass signature). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5222ab61-7d5e-4642-b75f-ef365e375f4f
[Pilot] PR Pipeline Failure AnalysisA CI pipeline failed on this pull request. Here is an automated analysis of what went wrong and how to get the build green. What failedThe Azure Pipelines build has three failures across two packages:
All other packages ( Recommended next steps
Raw pipeline analysis (azsdk ci analyze)
|
Pre-existing Build Analyze failures on the PR (independent of the sample/test and docs fixes): - verifysdist :: azure-ai-agentserver-core failed with "Source folder [samples] is not included in sdist". Core ships samples/state_store_sample.py but its MANIFEST.in lacked the samples include; added `recursive-include samples *.py *.md` (mirrors the invocations package, whose verifysdist passes). Verified locally: verifysdist now exits 0. - pylint :: core and :: invocations failed with C0301 (line-too-long) on the single-line module docstring at __init__.py:4 (136/120 and 164/120). The docstrings must stay single-line (apistub mis-parses multi-line package __init__ docstrings), so shortened the prose to fit within 120 columns (116 and 99 chars). Both still render cleanly (docutils-validated) and do not change api.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5222ab61-7d5e-4642-b75f-ef365e375f4f
Reviewer feedback on PR #46997: - README quickstart used the bare ``@task`` form and ``result.output``, both wrong: ``@task`` requires an explicit ``name=`` (validated in ``_validate_task_name``), and ``Task.run`` returns the handler's output directly (typed ``Output``), not a wrapper. Fixed to ``@task(name=...)`` and ``print(result)``. Applied the same ``@task`` -> ``@task(name=...)`` fix to the two examples in docs/streaming-guide.md. - MultiTurnTask.run was annotated ``-> Any``; tightened to ``-> Output`` to match Task.run (pyright clean). - Removed the unused ``from __future__ import annotations`` from streaming/__init__.py (no annotations in that module). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5222ab61-7d5e-4642-b75f-ef365e375f4f
…rn type The MultiTurnTask.run return-type change (Any -> Output) altered the generated API surface; regenerated api.md / api.metadata.yml with apiview-stub-generator 0.3.30 so the consistency check passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5222ab61-7d5e-4642-b75f-ef365e375f4f
Summary
Adds a resilient task primitive and an event-streaming API to
azure-ai-agentserver-core(2.0.0b7), plus a matching set of runnablesamples in
azure-ai-agentserver-invocations(1.0.0b6).The resilient-task primitive is a small, decorator-driven API that lets a
hosted agent run long operations as named tasks that survive process
crashes, out-of-memory kills, and container redeployments. The framework
persists task state and automatically re-invokes in-flight work after a
crash, so the developer doesn't have to write explicit checkpoint or
replay code.
Developer guides:
What ships in this PR
azure-ai-agentserver-core2.0.0b7azure.ai.agentserver.core.tasks): the@task(one-shot) and@multi_turn_task(multi-turn) decorators,TaskContext,TaskRun, configurable retries (RetryPolicy),cancellation, steering, and a typed exception set.
azure.ai.agentserver.core.streaming):publishes incremental task output to one or more subscribers with
in-memory and file-backed buffering and live/replay delivery — the
building block for Server-Sent Events responses a client can
disconnect from and resume.
azure-ai-agentserver-invocations1.0.0b6primitive:
resilient_copilot(streaming chat with crash recovery),resilient_multiturn(suspend/resume conversation),resilient_langgraph(LangGraph integration), andresilient_research(multi-stage research loop with checkpointing).
POST /invocations/{id}/cancel) and get(
GET /invocations/{id}) endpoints now resolve the session idconsistently with the invoke endpoint, so custom handlers can reliably
look up per-session state.
Out of scope (separate PRs)
This PR is the base of a stack. Follow-on work lives on its own branches:
azure-ai-agentserver-responsesresilient orchestration —branch
feature/agentserver-responses-spec016.feature/agentserver-durable-agent-demo(temporary showcase; not formerge).
Notes for reviewers
removed incidental formatting churn from unrelated files so the diff
reflects only the new feature plus version, changelog, and dependency
updates.
full design lives in the developer guides linked above.