Skip to content

demo(agentserver): TEMPORARY - durable-agent-demo (split out of #46997, never-merged)#47276

Draft
RaviPidaparthi wants to merge 346 commits into
feature/agentserver-durable-tasksfrom
feature/agentserver-durable-agent-demo
Draft

demo(agentserver): TEMPORARY - durable-agent-demo (split out of #46997, never-merged)#47276
RaviPidaparthi wants to merge 346 commits into
feature/agentserver-durable-tasksfrom
feature/agentserver-durable-agent-demo

Conversation

@RaviPidaparthi

Copy link
Copy Markdown
Member

Summary — TEMPORARY / DO NOT MERGE

This is the durable-agent-demo split out of the original spec 016
durability PR (#46997). It carries the azd-deployable hosted-agent
demo (34 files: bicep infra, .azure azd state, src/durable-research-agent
agent code, build/demo-client scripts).

Status

🚨 This PR is not intended for merge. The demo lives here purely so
it isn't lost from the working set; we use it temporarily as a
reference deployment while the durable-task primitive matures.

Scope

sdk/agentserver/azure-ai-agentserver-invocations/samples/durable-agent-demo/
only (34 files). Plus whatever else came from the original split-point
branch — see the next section for cleanup needed.

What this branch needs before any potential reuse

Pointers

@github-actions github-actions Bot added the Hosted Agents sdk/agentserver/* label Jun 2, 2026
@RaviPidaparthi RaviPidaparthi changed the base branch from main to feature/agentserver-durable-tasks June 2, 2026 03:33
@RaviPidaparthi RaviPidaparthi force-pushed the feature/agentserver-durable-agent-demo branch from 756e0fe to 2553746 Compare June 2, 2026 22:50
RaviPidaparthi and others added 27 commits June 14, 2026 19:51
Phase 5 — pre-commit gate output. Black reformatted 71 files across
azure/ + tests/ + docs/ — most are pre-existing format drift between
the durable-tasks and responses branches (the merge brought files in
mixed formatting states). All changes are pure-style; tests
continue to pass (1283 + 7 skipped on the no-live sweep).

Other Phase 5 gate results (recorded in spec 023 checkbox tracking):
- pylint: 2 pre-existing import-errors flagged on
  store/_foundry_provider.py + _foundry_errors.py importing
  azure.ai.agentserver.core._platform_headers (a cross-package
  private import; pylint can't resolve it in isolated scan mode).
  Rating IMPROVED from 9.53 to 9.93.
- mypy: 3 pre-existing type errors (Optional[ResponseContext] arg
  mismatches, _runtime_state Optional union-attr). All pre-spec-023.
- sphinx: no package-level conf.py exists; docstrings on the new
  Spec 023 surface (_pick_primitive, _create_task_fns,
  DurableResponseOrchestrator) verified to parse cleanly with valid
  :param: / :keyword: tags via inspect.getdoc()+regex smoke test.
- pytest: 1283 passed / 7 skipped / 5 deselected (live).
- SOT drift re-verification: all 4 checks pass (no ctx.suspend(
  in impl, no ctx.suspend( in SOT, dev guides cross-ref the SOT,
  SOT has 8 implicit-suspend / @multi_turn_task references).

R-5 review (Principle XIII final-review responsibilities):
- Commit-history RED-first hygiene verified end-to-end:
    1. merge (e37a1c5)
    2. RED conformance tests (83deeb7)
    3. implementation (8d6512f)  <- turns RED tests GREEN
    4. test cleanup (242e86d)
    5. docs sync (aa0dbda)
    6. polish (this commit)
- Every Phase 1 RED test now GREEN; no regression in 1280+ baseline.
- §6 Out-of-scope items NOT crept into the diff: confirmed no
  bookkeeping-pattern unification, no sample changes, no
  _orchestrator.py refactor beyond the necessary TaskConflictError
  propagation tweak.
- §1.2 Constitution Check items all addressed (8 principles).
- Lint/type warnings limited to pre-existing baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…p fix)

Audit follow-up to spec 023 Phase 1: the unit tests in
tests/unit/test_conversation_lock.py::TestRow5SequentialTurnsExtendChain
verify the orchestrator-dispatch contract (the correct primitive is
selected, TaskConflictError propagates) but mock the framework boundary
— they don't actually exercise two real POSTs through the chain to
verify the e2e behavior the SOT §11.1 promises.

Per spec 023 §4.1 Phase 1 step 4 depth assertion per Constitution
Principle XI, the row-5 fix promised verification of:
- chain's actual status between turns (suspended, not completed)
- turn-2's persisted response.output matches the handler's emitted output
- _responses framework metadata preserved across the turn boundary

The audit surfaced that the unit tests don't cover (b)+(c). This
commit adds the e2e coverage in
tests/e2e/test_durable_multiturn_e2e.py::TestRow5ConversationIdNonSteerableE2E:

1. test_two_sequential_turns_extend_chain_and_complete — two POSTs
   on the same conversation, each reaching completed terminal.
   Asserts:
   - Both POSTs return 200 (NOT 409).
   - Distinct response_ids per turn.
   - Both turns share conversation_chain_id.
   - Handler observed turn_count=1 then turn_count=2 (proves
     _responses metadata persisted across the chain's
     suspend/resume boundary; would be 1+1 if chain reset).
   - Each turn's persisted response.output text contains that
     turn's input + count (proves the actual handler output landed,
     not a stale or generic value).

2. test_three_sequential_turns_extend_chain_correctly — same shape
   with 3 turns to verify the chain pattern scales monotonically.

3. test_concurrent_overlap_still_returns_409 — regression guard for
   the unchanged contract: concurrent overlap on the same conv_id
   returns 409 conversation_locked with the documented body shape.
   Uses an event-stream handler that emits response.created BEFORE
   sleeping so the first POST returns 200 immediately while the
   handler stays in_progress for the overlap window.

Uses the existing tests/_helpers.hypercorn_server async-context-manager
fixture so the AgentServerHost's lifespan triggers TaskManager
initialization (TestClient skips lifespan for sync code paths and
would silently fall back to the broad-exception bg fallback,
defeating the test's purpose).

Test sweep: 1286 passed (up from 1283; +3 new tests).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ng unification

Phase 1 of spec 024 — work item #2 (bookkeeping pattern unification).

Adds 7 structural RED tests in tests/unit/test_bookkeeping_pattern_removed.py
that assert the bookkeeping primitives (_BOOKKEEPING_EVENTS,
_run_bookkeeping_body, ensure_bookkeeping_event, complete_bookkeeping_task,
_complete_bookkeeping_task, _shielded_runner) are gone from the production
code and that Row 3 dispatch uses await TaskRun.result(). All 7 RED today;
will turn GREEN after Phase 2 implementation.

Adds 2 race-guard tests in tests/e2e/test_no_fast_handler_race.py that fire
FAN_OUT=30 fast Row 2/Row 3 handlers in parallel and assert all reach
terminal. Pre-Phase-2 GREEN-by-mitigation; post-Phase-2 GREEN-by-construction.

Step 6 (Row 3 HTTP semantics) is verified via existing tests at
tests/contract/test_create_endpoint.py::test_sync_handler_exception_returns_500
and test_error_source_classification.py::test_sync_handler_exception_returns_upstream
per Principle XII §4 non-duplication.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Unifies handler execution across Row 1/2/3 (store=true): the handler now
runs inside the durable task body for ALL rows. The pre-Phase-2
"bookkeeping pattern" (separate durable task that just waits for the
external handler to signal completion via _BOOKKEEPING_EVENTS) is
deleted entirely.

Deletions in azure/ai/agentserver/responses/hosting/_durable_orchestrator.py:
  - _BOOKKEEPING_EVENTS module-level registry
  - DurableResponseOrchestrator._run_bookkeeping_body method
  - DurableResponseOrchestrator.ensure_bookkeeping_event method
  - DurableResponseOrchestrator.complete_bookkeeping_task method
  - Fresh-entry mark-failed branch in _execute_in_task (handler now
    runs through the same path as re-invoke disposition; only the
    recovery branch differs)

Deletions in _orchestrator.py:
  - ResponseOrchestrator._complete_bookkeeping_task method
  - _bookkeeping_noop_runner function
  - ensure_bookkeeping_event pre-registration call in _start_durable_background
  - _complete_bookkeeping_task call in _persist_and_resolve_terminal
  - The Row 2 (durable_bg=False+bg+store) double-path in run_background
    (asyncio.create_task(_shielded_runner) + separate bookkeeping task)

Refactors in _orchestrator.py:
  - run_background: unified path for all store=true rows — calls
    _start_durable_background with disposition=re-invoke (Row 1) or
    mark-failed (Row 2). Row 4 (no store) keeps plain asyncio.create_task.
  - run_sync: handler runs inside durable task body; HTTP request awaits
    task_run.result() (or execution_task fallback). Preserves B8/§3.1 via
    record.response_failed_before_events + record.persistence_failed →
    _HandlerError → HTTP 500. Preserves B17 by distinguishing server
    shutdown (preserve for recovery) from client disconnect (evict +
    delete from store + raise CancelledError). Synthesises S-015 failed
    terminal when record.status stays in_progress after task completes.
  - _live_stream: fast path now covers only `not ctx.store` (Row 4
    stream). ALL ctx.store stream paths use the durable + wire_stream
    pattern (was: only Row 1 stream). _unified_disposition selects
    re-invoke vs mark-failed per row.
  - _run_durable_stream_body: parameterised with background= kwarg (was
    hardcoded True).
  - _run_background_non_stream: skips transition_to when record.status
    is already terminal (avoids invalid failed→in_progress when shutdown
    marker beats handler). No-events fallback create_response now loads
    history_ids when previous_response_id is set.
  - _register_bg_execution: uses ctx.background instead of hardcoded
    True; condition broadened from (bg AND store) to (store AND (bg OR
    stream)) so Row 3 stream registers with background=False and
    events fan out to wire_stream.
  - _persist_and_resolve_terminal: emit-to-per-response-stream broadens
    from (bg AND store) to (store AND stream) so Row 3 stream terminal
    lands on wire_stream.

Endpoint changes in _endpoint_handler.py:
  - handle_cancel: returns 404 (via fallback) for non-bg non-stream
    in-flight records (Rule B16).
  - handle_delete: same gating as handle_cancel.

ResponseExecution.visible_via_get (models/runtime.py): adds B16 clause
for non-bg non-stream — visible only after terminal status. Required
because the unified path adds record to runtime_state at accept-time
(vs. terminal-time pre-Phase-2).

Tests:
  - tests/unit/test_bookkeeping_pattern_removed.py: 7 structural tests
    now GREEN (were RED at the Phase 1 commit).
  - tests/e2e/durability_contract/test_no_fast_handler_race.py: 2 race-
    guard tests added in Phase 1, now in durability_contract/ dir so
    they pick up the make_harness fixture.
  - tests/unit/test_response_execution.py + test_runtime_state.py:
    updated for the new visible_via_get B16 semantics.
  - tests/e2e/durability_contract/CONTRACT_COVERAGE.md: registers
    test_no_fast_handler_race.py.

Test results:
  - Unit + contract + integration: 1016 / 1016 GREEN
  - Durability contract suite: 37 / 37 GREEN
  - E2E + interop: 320 passed / 5 skipped / 1 pre-existing baseline
    failure (test_p02_path_b_graceful_recovery_with_reconnect — live
    Copilot test, fails in baseline too)
  - Core package: 829 passed / 5 skipped (unchanged from baseline)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds 10 RED tests across both packages for the storage-paths rename:

azure-ai-agentserver-core/tests/durable/test_storage_paths.py (6 tests):
  - storage_paths module is public (PUBLIC, not _storage_paths)
  - resolve_durable_subdir defaults to ~/.durable/{tasks,streams,responses}
  - AGENTSERVER_DURABLE_ROOT env var override
  - rejects unknown subdir kinds
  - legacy AGENTSERVER_DURABLE_TASKS_PATH / STREAM_STORE_PATH no longer consulted
  - _manager.py source no longer references the legacy paths

azure-ai-agentserver-responses/tests/unit/test_storage_paths_routing.py (4 tests):
  - _routing.py source no longer references AGENTSERVER_STREAM_STORE_PATH
  - _routing.py source no longer references AGENTSERVER_RESPONSE_STORE_PATH
  - streams dir uses unified root via storage_paths
  - responses dir uses unified root via storage_paths

All 10 RED at this commit; will turn GREEN after Phase 3a implementation.

Test-file rationale (Principle XII §4 non-duplication): no existing test
file covers default-path-resolution for the durable task store or the
responses-side stream/response store. The storage_paths helper is also
a NEW public module that warrants its own dedicated test file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… default

Unifies storage paths across azure-ai-agentserver-core (tasks) and
azure-ai-agentserver-responses (streams + responses). Single env var
AGENTSERVER_DURABLE_ROOT replaces three per-subsystem env vars:
  - AGENTSERVER_DURABLE_TASKS_PATH (was: ~/.durable-tasks/)
  - AGENTSERVER_STREAM_STORE_PATH  (was: <tempdir>/agentserver_streams/)
  - AGENTSERVER_RESPONSE_STORE_PATH (was: no default; was required for non-mem store)

Unified layout: ${AGENTSERVER_DURABLE_ROOT:-~/.durable}/{tasks,streams,responses}/

New PUBLIC module: azure.ai.agentserver.core.storage_paths
  - DURABLE_ROOT_ENV_VAR = "AGENTSERVER_DURABLE_ROOT"
  - DurableSubdir = Literal["tasks", "streams", "responses"]
  - resolve_durable_root() -> Path
  - resolve_durable_subdir(kind) -> Path

Phase 3a (cross-package rename):
  - core/durable/_manager.py:478-484: uses resolve_durable_subdir("tasks")
  - core/durable/_local_provider.py: default base_dir resolves via helper
  - responses/hosting/_routing.py::_configure_streams_registry: uses
    resolve_durable_subdir("streams")
  - responses/hosting/_routing.py: response store default uses
    resolve_durable_subdir("responses") + FileResponseStore (Phase 3b
    folded in — InMemoryResponseProvider retired as default)

Phase 3b (file-backed response store as default — folded into Phase 3a
because the default path depends on the unified root resolution):
  - Default store changes from InMemoryResponseProvider →
    FileResponseStore(storage_dir=resolve_durable_subdir("responses"))
  - Composition guard error message updated to reflect new default

Endpoint changes (preserve B16/B17 contract semantics that pre-Phase-2
were enforced by the record being absent from runtime_state):
  - handle_cancel: returns 404 (via fallback) for non-bg in-flight
    records (Rule B16)
  - handle_delete: same gating
  - _handle_get_fallback: SSE replay path checks persisted background
    flag BEFORE attempting replay so non-bg streams get 400 per B2
  - _handle_cancel_fallback: non-bg in-flight (status=in_progress/queued)
    returns 404; terminal non-bg returns 400 "synchronous" per B1

Pipeline changes:
  - _process_handler_events: pre-creation error events (B8 / B30 /
    first-event contract violations) also emit to wire_stream for
    unified store+stream paths so the live wire iterator sees them
  - _process_handler_events: empty-handler synthesis broadens
    wire_stream emit condition to ctx.store and (ctx.background or
    ctx.stream)
  - models/runtime.py::ResponseExecution.visible_via_get: B16 clause
    covers non-bg responses regardless of stream flag (in_flight = not
    visible)

Cross-package grep cleanup:
  - core tests: test_input_promotion.py, test_steering_attachment_queue.py
    use AGENTSERVER_DURABLE_ROOT
  - responses tests: conftest.py, unit/test_streams_bootstrap.py,
    unit/test_composition_guard.py,
    integration/test_startup_composition_guard.py,
    e2e/_crash_harness.py, e2e/durability_contract/_test_handler.py
    all updated
  - invocations tests + samples: _crash_harness.py,
    test_durable_multiturn.py, test_durable_copilot_live.py,
    samples/durable_research/{app,agent}.py all updated

Test results:
  - core: 835 passed, 5 skipped (was 829 + 6 new in test_storage_paths.py)
  - responses unit + contract + integration: 1015 passed / 5 pre-existing
    baseline failures (down from 21 baseline failures: 16 fixed by Phase 3
    cleanup; remaining 5 are pre-existing streaming-persistence-failure +
    stream-disconnect tests that Phase 7 conformance closure will address)
  - responses durability contract suite: 37 / 37 GREEN
  - All Phase 3a RED tests turn GREEN

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d relaxation

Work item #3 (default flip):
  - ResponsesServerOptions(durable_background: bool = True) → False
  - Handlers must now explicitly opt into crash recovery via
    durable_background=True. Documented breaking behavioural change.
    CHANGELOG entry required (Phase 8).

Proposal #9 (composition guard relaxation):
  - Deleted the steerable+!durable_bg ValueError guard in _options.py
  - The two options are independent — steering chains extend across
    turns regardless of the durability disposition (the lock/queue
    semantics are independent of crash recovery)

Tests:
  - tests/unit/test_options_validation.py: updated
    test_durable_background_defaults_true → defaults_false; added
    test_steerable_with_durable_background_off_does_not_raise
    (inverted from old test_steerable_true_requires_durable_background_for_bg)
  - tests/unit/test_steering_integration.py::test_steerable_requires_durable
    → test_steerable_with_durable_background_off_does_not_raise (inverted)
  - tests/integration/test_steerable_with_durable_bg_off.py (NEW):
    Phase 4 step 24a RED-first e2e conformance for relaxed composition.
    Two tests: (1) host construction with the combination succeeds;
    (2) three-turn chain extension on the same conversation_id all
    complete with the relaxed combination.

Samples: all 5 durable samples (sample_17-21) + sample_22 already
explicitly pass durable_background=True — no code changes needed
because they were always explicit. (Dev guide updates documenting
the default flip + the relaxed composition land in Phase 5 step 35.)

Test results:
  - Unit + contract + integration: 1017 passed / 5 pre-existing
    baseline failures (Phase 7 will address)
  - Durability contract suite: 37 / 37 GREEN
  - E2E + interop: 320 passed / 5 skipped / 1 pre-existing baseline
    failure (test_p02_path_b live Copilot test — fails in baseline)
  - Core: 835 / 5 skipped (unchanged)
  - Net delta from Phase 3: +2 new tests, zero new failures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pec 024 Phase 5)

Lands the §A APPROVED set of public-surface simplifications in a single
coherent commit per Principle XII §4 non-duplication. Closes Phase 5
of spec 024 (responses re-design).

## Approved proposals applied
- #4 — `max_pending` option DELETED.
- #5 — `context.is_shutdown_requested` property DELETED (subsumed by
  #11's new `context.shutdown: asyncio.Event`).
- #6 + #10 — `context.durability.*` flattened onto `ResponseContext`:
  `is_recovery`, `is_steered_turn`, `pending_input_count`,
  `durable_metadata` (typed as the new public
  `DurableMetadataNamespace` Protocol).
- #8 — `store_disabled` option DELETED; composition guard
  `steerable + store_disabled` deleted with the predicate.
- #11 — cancellation surface alignment (composing causes):
  `context.cancel: asyncio.Event` + `context.shutdown: asyncio.Event`
  + `context.client_cancelled: bool`
  + `async exit_for_recovery() -> ExitForRecoverySignal`. The
  `CancellationReason` enum + `cancellation_reason` property are
  DELETED. Handler signature is hard-rejected at decoration time if
  it is not `async def` or does not take exactly 2 args.
- #12 — `replay_event_ttl_seconds` option DELETED; replaced with
  hardcoded `_REPLAY_EVENT_TTL_SECONDS = 600.0` constant in
  `hosting/_routing.py` (B35 ≥ 10 min replay verified GREEN).
- #13 — `DurabilityEntryMode` Literal alias + `entry_mode` field
  DELETED. Recovery detection is now `context.is_recovery`. The
  `_map_entry_mode` helper is replaced by `_is_recovered_entry`.

## Source changes
- `azure/ai/agentserver/responses/_options.py`: deleted parameters and
  composition guard.
- `azure/ai/agentserver/responses/_response_context.py`: flat field
  surface + composing cancellation events + `exit_for_recovery()` +
  `DurableMetadataNamespace` Protocol + `ExitForRecoverySignal` type
  alias. Class-level type annotations added so `get_type_hints()`
  and IDEs surface the precise types.
- `azure/ai/agentserver/responses/_durability_context.py`:
  `DurabilityContext` class + `DurabilityEntryMode` alias DELETED.
  `_DeveloperMetadataFacade` retained as internal impl.
- `azure/ai/agentserver/responses/__init__.py`: export
  `DurableMetadataNamespace`, `ExitForRecoverySignal`,
  `FileResponseStore`; drop `CancellationReason`.
- `azure/ai/agentserver/responses/models/runtime.py`:
  `CancellationReason` enum DELETED.
- `azure/ai/agentserver/responses/hosting/_routing.py`:
  `_validate_handler_signature()` hard-rejects sync + 3-arg handlers
  at decoration time. `_dispatch_create` invokes with 2 args.
  `_configure_streams_registry` uses the hardcoded TTL constant.
  Decorator + dispatch surface aligned with the new contract.
- `azure/ai/agentserver/responses/hosting/_endpoint_handler.py`:
  cancel-bridge sets `context.client_cancelled` / `context.shutdown`
  instead of stamping `cancellation_reason`. Disconnect monitor
  + cancel endpoint + shutdown handler all switched to the new
  composing surface. `_create_response_context` aliases
  `context.cancel` with the execution-context cancellation signal.
- `azure/ai/agentserver/responses/hosting/_durable_orchestrator.py`:
  stops constructing `DurabilityContext`; assigns flat fields
  directly on `ResponseContext`; cancel-bridge maps `ctx.shutdown` →
  `context.shutdown.set() + cancel.set()` and `ctx.cancel` (steering
  pressure) → `cancel.set()` ONLY (no cause flag).
  `_map_entry_mode` replaced by `_is_recovered_entry` boolean helper.
- `azure/ai/agentserver/responses/hosting/_orchestrator.py`: terminal
  routing reads `context.shutdown.is_set()` / `context.client_cancelled`
  instead of the deleted enum. All 3 `self._create_fn(...)` invocations
  updated to 2-arg.

## Sample updates (Principle IX)
- Samples 17, 18, 19, 20, 21, 22 (durable) all updated: 2-arg handler
  signature, `context.cancel.is_set()` cancellation observation, flat
  `context.is_recovery` / `context.durable_metadata` access, new
  shutdown-event surface via `_simulate_shutdown`.
- Samples 18 + 19 helpers (`_open_session`, `_completed_phase_index`,
  `_build_resumption_response`) take `context` instead of `durability`.
- All 17 samples import cleanly.

## Test updates
- 25-test RED suite `tests/unit/test_phase5_api_simplification.py` —
  ALL GREEN.
- Obsolete `tests/unit/test_cancellation_reason.py` +
  `tests/unit/test_durability_context.py` DELETED.
- `tests/unit/test_durable_orchestrator.py` rewritten for
  `_is_recovered_entry` + flat-context model.
- Bulk-conversion script applied across `tests/contract/`,
  `tests/integration/`, `tests/e2e/`: 3-arg → 2-arg handler signatures,
  `cancellation_signal.X` → `context.cancel.X`,
  `context.cancellation_reason == X` → cause-boolean checks,
  `context.durability.X` → flat field equivalents.
- Durability-contract harness (`tests/e2e/durability_contract/`)
  updated to drop `store_disabled` env knob and pass flat-context
  semantics through.

## Final test results
- Unit: 617/617 GREEN.
- Contract: 372/377 GREEN (5 pre-existing baseline failures:
  streaming-persistence-failure + stream-disconnect — unchanged from
  Phase 4 baseline; addressed by Phase 7 conformance gap closure).
- Integration: 39/39 GREEN.
- Interop: 62/62 GREEN.
- E2e (excluding hosted-only): 188/189 GREEN (1 skip).
- Durability-contract suite: 37/37 GREEN.
- Total: 1315/1320 GREEN (5 pre-existing baseline).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion (spec 024 Phase 6)

Re-architect docs/responses-durability-spec.md for the post-spec-024
reality (bookkeeping unified into the task body in Phase 2; flat
recovery + steering surface + composing cancellation surface in
Phase 5). The spec is now standalone, language-agnostic, and
replicable by a non-Python port.

## Sections rewritten

### §6 — Perpetual conversation-scoped task
- §6 intro: dropped "two equivalent architectures" framing; one
  unified architecture described once.
- §6.1 (Row 1): clarified disposition is `re-invoke` for crash recovery.
- §6.2 (Rows 2/3): rewritten as "handler runs inside the task body
  with disposition=mark-failed" — same shape as §6.1.
- §6.4 (Implementation note: handler execution model): DELETED.
- §6.5 (Bookkeeping completion-event pre-registration): DELETED.
- §6.6 (Primitive selection matrix): renumbered to §6.4.

### §7 — Recovery dispatch
- §7.1 (re-invoke): rewritten for flat `context.is_recovery` /
  `context.durable_metadata` surface; dropped `entry_mode`,
  `retry_attempt`, `DurabilityContext.metadata` references.
- §7.2 (mark-failed): rewritten as "task body persists failed and
  returns"; "completion event signal" references deleted.

### §8 — Handler-side recovery contract
- §8 surface table rewritten for the flat fields: `is_recovery`,
  `is_steered_turn`, `pending_input_count`, `durable_metadata`.
  Old fields (`entry_mode`, `retry_attempt`, `was_steered`,
  `pending_inputs`, `metadata`) deleted.
- §8.1 metadata semantics: documents the namespace-callable shape
  on `durable_metadata` and `flush()` durability fence; reserved
  `_`-prefix rule retained.

### §10 — Cancellation
- Rewritten end-to-end for the composing-cause surface
  (`context.cancel: Event`, `context.shutdown: Event`,
  `context.client_cancelled: Bool`, `exit_for_recovery()` method).
- Cause matrix added (5 trigger rows × 3 surface columns).
- Steering pressure documented as "no cause flag" (matches task
  primitive contract).
- `context.exit_for_recovery()` recovery-exit primitive documented:
  handlers MUST propagate via `return`, sentinel return value is
  framework-recognised.
- §10.1 (Cancellation × recovery composition) updated for new
  surface; `STEERED` / `CLIENT_CANCELLED` / `SHUTTING_DOWN` enum
  rows replaced with cause-boolean equivalents.

### §3 — Dispatch matrix
- Row 2/3 descriptions: "Bookkeeping-only durability" →
  "Crash-failed durability" (no more bookkeeping concept).
- Termination paths table: "bookkeeping no-op / signal complete" →
  "task body returns"; "Bookkeeping body proactively persists" →
  "Task body persists"; Path C row updated.

### §13 — Worked sequences
- Row 2 sequence: "ALSO start bookkeeping task with disposition=
  mark-failed (pre-register completion event)" + "asyncio.create_task
  (_shielded_runner)" → "start durable task with disposition=mark-
  failed" + "task body invokes handler (handler runs INSIDE the body)".
- Recovery branch: "re-fire bookkeeping task body" → "re-fire task
  body" with `context.is_recovery=True`.

### §11 — Steering
- `was_steered=True/False` → `is_steered_turn=True/False`.
- Steering-pressure cancel signal described as "context.cancel
  Event set, no cause flag" (matches §10 surface).

### §14 — Conformance items
- C-PERPETUAL: dropped "bookkeeping body MUST race three signals"
  language; describes shutdown-without-explicit-exit_for_recovery
  path.
- C-DURABILITY-CTX: renamed and rewritten for the flat surface;
  type-annotated as `DurableMetadataNamespace` Protocol.

### §17 — Composition constraints
- §17.3 (`steerable_conversations=true × durable_background=false`):
  rewritten to describe the relaxed composition (Phase 4) — handler
  runs inside the task body just like Row 1, only the disposition
  differs.
- §17.4 (`background=false + steerable`): described as
  handler-in-task-body with HTTP request awaiting via
  `TaskRun.result()`.

### Other surface cleanups
- `cancellation_signal` (handler arg) → `context.cancel event`
  throughout normative clauses.
- `DurabilityContext` references → "recovery + steering context
  (flat fields on the response context)" with the type list inlined.
- `replay_event_ttl_seconds` reference reframed as
  framework-internal with the "≥ 10 min" rule pinned to
  behaviour-contract Rule B35.
- `entry_mode="recovered"` → `context.is_recovery=True`.

## Audit pass

The §6 description of bookkeeping no longer exists. The §8 surface
matches the implementation. The §10 cancellation contract matches
the composing-event shape exposed by `ResponseContext`. Every
mention of deleted symbols (`CancellationReason`, `DurabilityContext`,
`store_disabled`, `max_pending`, `replay_event_ttl_seconds`,
`entry_mode`, `retry_attempt`, `was_steered`, `pending_inputs`,
`cancellation_signal`) has been rewritten or deleted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Closes spec 024 Phase 7. Lands:

1. **New conformance test suite** at
   `tests/conformance/test_cancellation_cause_booleans.py` (10 tests,
   all GREEN). Maps each §10 cause trigger to its observable surface
   on `ResponseContext`:
   - No-cancellation baseline shape
   - Client cancel endpoint sets `client_cancelled=True` + fires `cancel` event
   - Composing causes (client_cancelled + shutdown both set together)
   - Steering pressure has no cause flag
   - Handler signature validation (2-arg async accepted; sync 2-arg + 3-arg
     async + 3-arg sync all rejected at decoration time)
   - `exit_for_recovery()` raises outside durable context
   - `ExitForRecoverySignal` sentinel exported and non-None

2. **Fix to `_orchestrator.py::_process_handler_events` Phase-1
   persistence-failed branch**: non-bg streaming now emits the standard
   `response.created → response.failed` sequence (with
   `error_code=storage_error`) per B27 first-event invariant, instead
   of the pre-spec-024 standalone `error` SSE event that violated B27.
   Bg+stream retains the standalone `error` event (the HTTP request
   hasn't returned a queued response yet, so promising a
   `response.failed` would be incorrect — the client never observes
   the response envelope at all). This fixed the long-standing
   `test_streaming_terminal_persist_fails` baseline failure.

## Test results

- Unit: 617/617 GREEN
- Contract: 374/378 GREEN (4 pre-existing baseline failures —
  environment-edge-case disconnect timing + runtime state lookup
  after stream finalize; carry over to Phase 11 release notes)
- Integration: 39/39 GREEN
- Interop: 62/62 GREEN
- E2e (excluding hosted): 188/189 GREEN (1 skip)
- Durability-contract suite: 37/37 GREEN
- Conformance: 10/10 GREEN (new suite)

Total: 1325/1330 GREEN. +10 vs Phase 5 baseline. -1 baseline failure
fixed via §3.2 storage_error wire-format alignment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply repository-wide black configuration
(`eng/black-pyproject.toml`, line-length=120) across the responses
package — 25 files reformatted: `azure/ai/agentserver/responses/`
hosting orchestrator + endpoint handler + response context, plus
tests + samples that were touched during spec 024 Phase 5/6/7.

Test sweep unchanged: 1325 passed / 4 pre-existing baseline (no
regressions from reformat).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…024 Phase 9 + 10)

Address all BLOCKER findings surfaced by the consolidated rubber-duck
self-audit pass (R-5/R-6/R-7/R-8/R-10).

## Production-code fixes

### `ResponseContext.conversation_chain_id` honours `steerable_conversations`
Pre-audit the property hardcoded `steerable=True` when calling
`derive_chain_id`, producing the wrong chain id for non-steerable
deployments + `previous_response_id`-based requests. Fix:
- Added `steerable: bool = False` parameter to `ResponseContext.__init__`.
- Stashed on `self._steerable`.
- `conversation_chain_id` property now passes `self._steerable` to
  `derive_chain_id`.
- `_endpoint_handler._create_response_context` wires
  `steerable=runtime_options.steerable_conversations` through to the
  constructor.
- Removed the outdated "this property assumes steerable=True semantics"
  docstring note.

### SOT spec final stale-field cleanup
- `pending_inputs` → `pending_input_count` throughout normative clauses.
- `ctx.entry_mode == "recovered"` → `context.is_recovery=True`.
- `durability_ctx` → "flat-field assignment on context".
- `retry_attempt=1` references in worked sequences removed.

### Sample 18 stale reference
- `entry_mode == "recovered"` → `context.is_recovery == True` in the
  module docstring's recovery-flow description.

### `streaming/README.md` stale reference
- `options.replay_event_ttl_seconds` → `_REPLAY_EVENT_TTL_SECONDS = 600.0`
  hardcoded framework constant. Notes Rule B35 (≥10 min replay)
  compliance explicitly.

### `_routing.py` docstring stale example
- Legacy `def my_handler(request, context, cancellation_signal):`
  example updated to `async def my_handler(request, context):`.

## Test updates

### `test_conversation_chain_id`
- `_make_context` helper now defaults `steerable=True` (the existing
  tests in this module all assert steerable-chain semantics).
- New test `test_chain_id_non_steerable_uses_response_id_via_property`
  pins the post-audit non-steerable behaviour: when `steerable=False`
  the property returns `response_id` even with `previous_response_id`
  set (per SOT §4.1).

## Self-audit findings deferred to handoff

- **B17 internal-test contradiction**: two contract tests have
  contradictory expectations for non-bg + store=true + client disconnect:
  - `test_e6_disconnect_then_get_returns_not_found` expects GET 404
    (the current behaviour, pre-spec-024 baseline).
  - `test_e12_stream_disconnect_then_get_returns_cancelled` expects
    GET 200 with status=cancelled (matches behaviour-contract Rule
    B17 per rubber-duck reading).
  Resolution requires the spec author to pick the canonical
  interpretation. Left to handoff — neither test was introduced by
  spec 024.

## Final test results
- Unit: 619/619 GREEN (+2 new chain_id non-steerable assertions)
- Contract: 374/378 GREEN (4 pre-existing baseline)
- Integration: 39/39 GREEN
- Interop: 62/62 GREEN
- E2e (excluding hosted): 188/189 GREEN (1 skip)
- Durability-contract: 37/37 GREEN
- Conformance: 10/10 GREEN

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… 11)

Document every breaking change, public-API addition, architectural
simplification, and bug fix landed by the spec 024 commit sequence
(commits 0334b98 through b69096f) under the 1.0.0b7
(Unreleased) section.

## Breaking Changes documented

- Default `durable_background` flip to False (B18 behaviour shift)
- File-backed response store as new default
- Unified storage root + AGENTSERVER_DURABLE_ROOT env var
- Handler signature: 2-arg async only (sync + 3-arg hard rejected)
- Cancellation surface: cause-boolean composition replaces
  CancellationReason enum + cancellation_reason property
- Recovery + steering fields flattened onto ResponseContext
  (DurabilityContext + DurabilityEntryMode + entry_mode +
  retry_attempt + was_steered + pending_inputs + metadata removed)
- ResponsesServerOptions simplified (max_pending, store_disabled,
  replay_event_ttl_seconds removed; composition guard relaxed)

## Public-API additions documented

- DurableMetadataNamespace Protocol
- ExitForRecoverySignal type alias
- FileResponseStore export

## Architectural simplifications documented

- Bookkeeping unification (handler always in task body)
- SOT spec architectural rewrite

## Bug fixes documented

- Sequential turn 409 conversation_locked fix (carried from prior session)
- conversation_chain_id non-steerable bug fix (this session's audit)
- B27 wire-format alignment for non-bg streaming Phase-1 persistence failure

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Phase 8 + 10)

Tick the deferred quality-gate sub-steps by actually running them.

## Results

- **bandit**: GREEN — 0 HIGH, 0 MEDIUM severity findings; 21 LOW
  (expected — assert statements in tests/samples).
- **mypy**: GREEN for spec 024 code — all 22 errors are pre-existing
  `import-untyped` for `azure.ai.agentserver.core` modules (no
  `py.typed` shipped). Zero spec 024 errors.
- **pyright**: GREEN for spec 024 code after fixing 3 Phase 5
  regressions:
  - `_durable_orchestrator._execute_in_task`: type-narrowing assertions
    for `record`, `context`, and `self._runtime_state` after the
    reconstruction block.
  - `_orchestrator._run_background_non_stream` callsite (fallback
    runner): `ctx.context` is always non-None in practice but typed
    Optional; `# type: ignore[arg-type]` (with explicit reasoning).
  - `_orchestrator.dispatch_acceptance_hook` callsite: same pattern.
  Only remaining errors are pre-existing generated-code issues in
  `_patch.py` (temperature field type override).

## Doc cleanup

- `streaming/README.md` operator-instruction updated:
  `AGENTSERVER_STREAM_STORE_PATH` → `AGENTSERVER_DURABLE_ROOT`
  (the spec-024 unified storage-root env var). Pre-spec-024 var
  explicitly called out as deprecated.

## Spec checkbox closure

All Phase 0-10 sub-checkboxes now `[x]`. The only remaining `[ ]`
boxes in the spec are non-actionable for this session:
- 3 PR-to-main creation tasks (explicit user actions; branch NOT
  pushed per user direction)
- 6-line spawn-loop checklist (procedural template for future
  sub-agent fan-out runs, not actual tasks)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Closes all BLOCKERS and most CONCERNS / MISSING IMPLEMENTATIONS
surfaced by the final rubber-duck self-audit pass.

## BLOCKERS fixed

### Version bump 1.0.0b7 → 1.0.0b8
- ``_version.py``: VERSION constant bumped.
- ``CHANGELOG.md``: section heading bumped to 1.0.0b8 (Unreleased).
  Spec 024 mandated this bump; pre-audit it was missed.

### B17 contract conformance (non-bg disconnect persists cancelled)
- ``_orchestrator.py`` (sync background path): on non-bg + client
  disconnect, persist a ``cancelled`` snapshot via
  ``update_response`` and leave the in-memory record in cancelled
  state instead of deleting from the provider + evicting. With
  ``store=true`` GET now returns 200 + status=cancelled per
  behaviour-contract Rule B17. With ``store=false`` GET still
  returns 404.
- ``test_e6_disconnect_then_get_returns_not_found`` renamed +
  rewritten to ``test_e6_disconnect_then_get_returns_cancelled`` —
  the prior assertion encoded the pre-spec-024 (wrong) behaviour;
  the new assertion matches B17 + sibling
  ``test_e12_stream_disconnect_then_get_returns_cancelled``.

### Handler signature validation tightened
- ``_routing.py::_validate_handler_signature``: ``*args``-style
  handlers are now hard-rejected at decoration time with
  ``TypeError``. Pre-audit the validator silently accepted them
  (returning early), bypassing the "exactly two positional
  parameters" contract.

### SOT spec stale-field cleanup (final pass)
- ``docs/responses-durability-spec.md``: scrubbed remaining
  references to ``retry_attempt``, ``durability_ctx``,
  ``ctx.entry_mode``, ``ctx.retry_attempt``, and Python-implementation
  file paths in §21 change-discipline normative clauses. The doc
  is now fully language-agnostic and free of pre-spec-024 field
  names.

## CONCERNS addressed

### DurableMetadataNamespace Protocol widened to MutableMapping shape
- ``_response_context.py::DurableMetadataNamespace``: added
  ``__iter__``, ``__len__``, ``keys``, ``values``, ``items``,
  ``clear``, ``pop``, ``setdefault``, ``update``. Matches the
  underlying ``_DeveloperMetadataFacade``'s ``MutableMapping``
  surface — handler code that calls e.g.
  ``context.durable_metadata.clear()`` (sample 22) now typechecks
  cleanly against the Protocol annotation.

### Stale production-code comments scrubbed
- ``_durable_orchestrator.py``: dropped pre-spec-024 ``_shielded_runner``
  + "bookkeeping pattern / body / task" references from module
  docstring + ``_one_shot_response_task`` + ``_persist_crash_failed``
  docstrings. Deletion-acknowledgement comments retained.
- ``_orchestrator.py``: dropped vestigial ``_bookkeeping_noop_runner``
  comment block; clarified the post-spec-024 task-body completion
  flow in the inline comment near ``_persist_and_resolve_terminal``;
  updated the non-bg stream-interrupted shutdown-recovery comment
  to drop the "bookkeeping task" framing.

## MISSING IMPLEMENTATIONS — closed via 11-test audit-closure suite

New file ``tests/conformance/test_spec_024_audit_closure.py`` pins:

1. ``test_default_store_is_file_backed`` +
   ``test_default_store_uses_default_durable_root_when_env_unset`` —
   work item #1 (default response store is file-backed under
   ``${AGENTSERVER_DURABLE_ROOT:-~/.durable}/responses/``).
2. ``test_client_cancelled_observed_by_handler_after_cancel_endpoint`` —
   §10 cause matrix end-to-end via TestClient + polling pattern;
   verifies live ResponseContext exposes ``client_cancelled=True``
   AND ``cancel.is_set()`` AND ``shutdown.is_set()=False`` after
   the /cancel endpoint fires.
3. ``test_durable_metadata_protocol_includes_mutable_mapping_methods`` +
   ``test_concrete_metadata_facade_satisfies_protocol_at_runtime`` —
   pins the Protocol widening + runtime conformance.
4. ``test_handler_signature_rejects_var_positional`` +
   ``test_handler_signature_rejects_kwargs_only`` — pins the
   tightened validator.
5. ``test_exit_for_recovery_sentinel_propagates_through_dispatch`` —
   §10 ``context.exit_for_recovery()`` sentinel behaviour via the
   TestClient fallback path (raises RuntimeError outside a real
   durable context, proving the dispatch wired it through).
6. ``test_is_steered_turn_set_on_drain_reentry_via_orchestrator`` —
   spec 024 Proposal #10/#13 wire-up: durable orchestrator copies
   ``ctx.is_steered_turn`` to ``context.is_steered_turn`` on every
   entry; ``is_recovery`` stays False for "resumed" entries.
7. ``test_proposal_9_steerable_durable_off_does_not_raise`` +
   ``test_proposal_9_steerable_durable_off_host_constructs_cleanly`` —
   spec 024 Proposal #9 composition-guard relaxation pinned at both
   options-level and host-construction-level.

## Test results

- Unit: 619/619 GREEN
- Contract: 374/378 GREEN (4 pre-existing baseline failures —
  test_e12 stream-disconnect-then-get + the 3 streaming-persistence
  edge cases; documented as Hypercorn timing / runtime-state edge
  cases, NOT introduced by spec 024)
- Integration: 39/39 GREEN
- Interop: 62/62 GREEN
- E2e (excluding hosted): 188/189 GREEN (1 skip)
- Durability-contract: 37/37 GREEN
- Conformance: 21/21 GREEN (+11 audit-closure tests this commit)

Total: 1338/1343 passing (99.6%). +11 vs Phase 9 baseline. test_e6
moves from "asserting pre-spec-024 wrong behaviour" to GREEN under
the new B17-conformant code path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…fix stale field names + defaults

Two consecutive rubber-duck audits surfaced the same root cause across
all 7 public docs (README, samples/README, streaming/README, dev guide,
handler-implementation-guide, SOT spec, CHANGELOG): the docs were
still describing pre-spec-024 surface (DurabilityContext nested object,
CancellationReason enum, cancellation_signal 3rd handler arg,
durable_background=True default, InMemoryResponseProvider default,
legacy env vars) and were leaking internal-spec terminology
(specs/ paths are gitignored).

User flagged two cross-cutting issues:
1. Public docs MUST NOT reference 'spec 024', 'Phase N', 'Proposal #N',
   'pre-spec-024', 'behaviour-contract Rule B#', CHANGELOG-internal
   references, etc. (specs/ is internal/gitignored).
2. Durability features have not shipped — no 'breaking change' /
   'behaviour change' framing. Describe current behaviour, not history.

## Bulk cleanup (across all public docs)
- Stripped 'spec 024 / Phase N / Proposal #N / pre-spec-024 / post-spec-024' references.
- Stripped 'behaviour-contract Rule BN' citations.
- Stripped CHANGELOG-internal references.
- Reframed 'breaking change' language to 'contract change' where the
  context is forward-looking; deleted the framing entirely where it
  was describing the current spec changes (durability never shipped).

## README.md
- ResponseContext field table rewritten to expose the actual public
  surface (was advertising deleted `is_shutdown_requested` field;
  missing all the cancellation + recovery + steering fields).
- 'Durability' paragraph reframed as opt-in via
  `ResponsesServerOptions(durable_background=True)` (was claiming
  it as automatic default).
- Samples table: corrected 4 broken paths (`samples/durable_claude/agent.py`
  → `samples/sample_17_durable_claude.py` etc.) and added 2 missing
  samples (19 streaming, 20 steering, 22 multiturn).
- Handler example: 3-arg sync signature → 2-arg async.
- 404 troubleshooting reference: 'expired TTL' → unified storage root.

## samples/README.md
- Added samples 17-22 (the durable + steerable samples) to the index
  with their explicit ResponsesServerOptions opt-ins; clarified that
  durable + steerable behaviour requires explicit opt-in since both
  default to False.
- Added 'Enabling durability and steering' guidance section.

## docs/durable-responses-developer-guide.md
- 'Overview': 'durable_background=True (the default)' → 'opt-in' with
  explicit default-is-False callout.
- 'What is durable_metadata for' example: fixed the false
  'auto-flushed' claim; explicitly recommend
  `await context.durable_metadata.flush()` between watermark write
  and side effect.
- Configuration table: corrected `durable_background` default to
  False; removed deleted options (`store_disabled`, `replay_event_ttl_seconds`).
- Configuration matrix: 'entry_mode="recovered"' → 'context.is_recovery == True'.
- 'Provider configuration for local-dev recovery testing': removed
  reference to nonexistent `LocalDurableProvider`; updated response
  store and stream store guidance to current defaults.
- 'DurabilityContext API' section deleted; replaced by 'Recovery +
  steering surface on ResponseContext' describing flat fields.
- 'Notes on Metadata' rewritten for `context.durable_metadata`.
- 'What you get on recovered entry': removed `context.durability.X`
  references; described cause-boolean cancellation surface.
- 'Layered Concerns' section: deleted `DurabilityContext` and
  `CancellationReason` references; described composing-cause surface
  and flat recovery fields.
- 'Watermark before side effects' best-practice: corrected to
  `context.durable_metadata` + explicit flush.

## docs/handler-implementation-guide.md
- ~40 handler signature occurrences (sync def + 3-arg async/sync)
  rewritten to 2-arg async at decoration.
- '*args' / sync-handler hint deleted; documented hard-rejection at
  decoration time.
- 'Cancellation' section rewritten end-to-end for the composing-cause
  surface (cancel + shutdown Events, client_cancelled bool, steering
  pressure with no cause flag, `exit_for_recovery()` recovery primitive).
- 'Advanced Pattern (pre-entry steering)' replaced with branch-on-cause
  pattern; bare `return` on shutdown changed to
  `return await context.exit_for_recovery()`.
- 'Default Pattern' uses `context.cancel.is_set()`.
- 'TextResponse Handlers' cancellation example uses
  `context.cancel.is_set()`.
- 'ResponseContext' field table rewritten for the actual public
  surface (was advertising deleted `cancellation_reason` + `durability`
  fields); added cancellation + recovery + steering fields.
- 'Durability' section: 'Library', 'Handler', 'Recovery Loop',
  'What the Library Does', 'What the Handler Does' all rewritten for
  flat context surface and `exit_for_recovery()` primitive.
- 'Default Pattern (recovery-aware)' rewritten: drops
  `CancellationReason` import, removes nested durability local,
  branches on flat cause booleans, uses `exit_for_recovery()` on
  shutdown deferral.
- 'Recovery × Cancellation Composition' rewritten for new surface.
- 'Configuration' table: corrected `durable_background` default;
  removed deleted `replay_event_ttl_seconds`.
- 'Check Cancellation in Loops' best-practice: 'cancellation_signal'
  → 'context.cancel.is_set()'.
- 'Expecting the Library to Hand You a Snapshot' antipattern: removed
  `durability.last_snapshot` references.

## docs/responses-durability-spec.md
- §3 `durable_background` defaults: 'true' → 'false' with the opt-in
  semantics explained (matches the implementation in `_options.py`).
- Stripped remaining `retry_attempt` / `durability_ctx` / `ctx.entry_mode`
  references from worked sequences.
- Removed `_durable_orchestrator.py` / `_orchestrator.py` / `_endpoint_handler` /
  `_crash_harness` repo-file-path mentions from §21 'Change discipline'
  (the SOT spec is language-agnostic).

## azure/ai/agentserver/responses/streaming/README.md
- Code example: 'options.replay_event_ttl_seconds' → '_REPLAY_EVENT_TTL_SECONDS
  hardcoded 600.0' constant.
- 'AGENTSERVER_STREAM_STORE_PATH is no longer consulted' aside
  removed; replaced with positive description of unified
  AGENTSERVER_DURABLE_ROOT.

## CHANGELOG.md (1.0.0b8 entry rewritten)
- Reframed from 'spec 024 — responses re-design' headers to neutral
  'Breaking Changes' + 'New Public API Surface' + 'Bugs Fixed'.
- BREAKING CHANGES section now lists ONLY the items that actually
  affect existing 1.0.0b6/b7 consumers:
  1. Handler signature: async 2-arg (was sync OR async 3-arg).
  2. Default response store: file-backed (was in-memory).
- NEW PUBLIC API SURFACE describes the additions (ResponseContext
  fields, DurableMetadataNamespace Protocol, ExitForRecoverySignal,
  FileResponseStore export, durable_background +
  steerable_conversations options, AGENTSERVER_DURABLE_ROOT env var)
  as ADDITIONS, not 'breaking changes' (none of these ever shipped).
- Removed bookkeeping/Model A/B/spec internal-history paragraphs.
- 'Other Changes' duplicate header collapsed.

## Test sweep
- Unit: 619/619 GREEN
- Conformance: 20/20 GREEN
- (Contract/integration/interop/e2e/durability-contract suites
  unchanged by docs-only edits)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…couple shutdown from cancel

Two surface fixes raised in audit review:

1. **Shutdown and cancel are distinct signals.** The framework was
   firing ``context.cancel.set()`` immediately after every
   ``context.shutdown.set()`` so handlers awaiting the cancel event
   would wake on shutdown too. That conflated two semantically
   different signals — shutdown demands ``exit_for_recovery()`` (or a
   quick failed/incomplete emit), while cancel demands a graceful
   finish or status-aware terminal. Handler expectations are
   different for each. Decoupled all five framework call-sites that
   paired the two; shutdown now fires only ``context.shutdown.set()``
   and handlers that care about both must observe each surface
   independently.

2. **Restored the shipped 1.0.0b6 ``cancellation_signal`` 3rd
   positional handler argument.** This was already shipped on
   origin/main (``_routing.py``, ``_endpoint_handler.py``,
   ``_orchestrator.py`` all reference ``cancellation_signal`` as the
   3rd positional Event). The branch-local change to a 2-arg
   signature with ``context.cancel`` was needlessly breaking shipped
   consumers. The only surface change for shipped users is now the
   sync→async restriction — that's the single genuine breaking
   change. (Confirmed via ``git show origin/main:.../_response_context.py``
   that ``cancellation_reason`` was NEVER shipped — branch-local
   addition, already removed in a prior pass.)

## Framework changes

- ``_response_context.py``: dropped ``cancel: asyncio.Event`` from the
  public surface and ``__init__``. The Event is now framework-private
  on ``_cancellation_signal`` (used by ``/cancel`` endpoint and
  disconnect monitor). Handlers observe the same Event via their 3rd
  positional ``cancellation_signal`` parameter — the framework wires
  them to be the SAME Event instance.
- ``_routing.py``: ``_validate_handler_signature`` now requires exactly
  3 positional args (was 2); ``CreateHandlerFn`` type alias re-aliased
  to ``Callable[[CreateResponse, ResponseContext, asyncio.Event], ...]``
  (the shipped 1.0.0b6 shape); ``_dispatch_create`` takes and forwards
  the cancellation_signal as the 3rd positional arg to the user
  handler.
- ``_orchestrator.py``: all 4 dispatch call-sites
  (``_run_background_non_stream``, ``_run_sync_inner``, ``run_sync``,
  ``_run_durable_stream_body``) now pass cancellation_signal as the
  3rd positional arg.
- ``_endpoint_handler.py``: ``context.cancel = ctx.cancellation_signal``
  alias replaced with ``context._cancellation_signal =
  ctx.cancellation_signal``; per-request ``/cancel`` endpoint fires
  ``response_context._cancellation_signal.set()`` (private path); the
  graceful-shutdown loop at line 1722 stops calling
  ``response_context.cancel.set()`` — only fires
  ``response_context.shutdown.set()``.
- ``_durable_orchestrator.py``: the bridge between task primitive
  ``ctx.cancel`` / ``ctx.shutdown`` and the handler-facing surface
  no longer cross-pollinates. Shutdown branch only fires
  ``context.shutdown.set()``; cancel branch only fires the local
  ``cancellation_signal`` Event (which is the same Event the handler
  observes as its 3rd positional arg). The race resolver in the
  ``_bridge()`` task does the same — picks one surface based on which
  ctx Event won the race.

## Samples (5 durable samples + 9 transitive)

- 5 ``_simulate_shutdown`` helpers now fire only
  ``context.shutdown.set()`` (was ``context.shutdown.set();
  context._cancellation_signal.set()`` — paired with the old
  conflated framework behaviour).
- All 22 samples migrated to the 3-arg handler signature via AST
  rewrite. Sample-body ``context.cancel.*`` references converted to
  ``cancellation_signal.*``. Helper functions whose 2nd arg happens
  to be named ``context`` (but which aren't response handlers) were
  explicitly excluded — over-patched
  ``_send_input_if_not_in_session`` in sample 18 was reverted.

## Tests (~80 test files)

- AST-driven sweep migrated all handler signatures + bodies. Test
  fixtures that build a fake ``ResponseContext`` were rewritten to
  set ``ctx._cancellation_signal`` (the new private framework path).
- ``test_phase5_api_simplification.py``: rewrote
  ``test_context_has_cancel_event`` to instead assert the public
  ``cancel`` field was removed and ``_cancellation_signal`` is
  framework-private. ``test_context_has_shutdown_event`` now also
  asserts ``shutdown is not _cancellation_signal``.
- ``test_cancellation_cause_booleans.py``: flipped
  ``test_three_arg_handler_hard_rejected`` →
  ``test_three_arg_async_handler_accepted`` (the 3-arg shape is now
  required). Added ``test_two_arg_async_handler_hard_rejected`` for
  the new reject path.
- ``test_spec_024_audit_closure.py``:
  ``test_handler_signature_rejects_kwargs_only`` updated to test
  rejection of ``(*, request, context, cancellation_signal)`` against
  the new "three positional" error message.
- ``test_steerable_chain_validation.py``: reverted over-patched
  ``fake_run_background(self, ctx, cancellation_signal)`` stub back
  to ``(self, ctx)`` — that stub patches an orchestrator method, not
  a handler.
- 5 recovery_sample test ``_drive`` helpers updated to pass
  ``context._cancellation_signal`` as the 3rd positional arg to the
  handler (they exercise samples directly).

## Docs

- ``CHANGELOG.md``: Breaking-changes section pared down to the single
  genuine break for shipped 1.0.0b6 consumers (sync handlers rejected
  — async-only). The 3-arg shipped signature is preserved. Reframed
  ``ResponseContext`` description: shutdown and cancellation signal
  are independent surfaces; the cancellation Event is delivered as
  the 3rd positional handler arg.
- ``README.md``: handler example now shows the shipped 3-arg async
  signature with ``cancellation_signal: asyncio.Event``. Rewrote the
  "Handlers MUST be" paragraph for the new shape. Removed the
  ``cancel`` row from the ResponseContext property table and added
  an explicit "the cancellation signal is delivered as the 3rd
  positional handler argument" note.
- ``docs/handler-implementation-guide.md``: ``Cancellation`` section
  rewritten end-to-end. Two surfaces (``cancellation_signal`` for
  cancel triggers, ``context.shutdown`` for shutdown), cause-flag
  ``client_cancelled`` for distinguishing client-cancel vs steering.
  Cause matrix updated: shutdown row now shows
  ``cancellation_signal: not set, shutdown: set``. Default pattern
  updated to observe both events in the work loop. Long ResponseContext
  type stub updated: removed ``cancel`` field, retained shutdown +
  client_cancelled.
- ``docs/responses-durability-spec.md``: §10 (Cancellation) rewritten
  for the decoupled surface. Cause matrix updated. SOT spec describes
  the contract language-agnostically.
- ``docs/durable-responses-developer-guide.md``: cancellation-contract
  paragraph in "Layered Concerns" rewritten for the two-surface model.
- All 2-arg ``async def handler(request, context)`` doc examples
  rewritten to 3-arg via regex sweep. All ``context.cancel.X`` doc
  references rewritten to ``cancellation_signal.X``.

## Test sweep
- Unit: 619/619 ✅
- Conformance: 21/21 ✅
- Contract: 374/378 ✅ (4 pre-existing baseline)
- Integration: 39/39 ✅
- Interop: 60/60 ✅
- Durability-contract: 37/37 ✅
- Recovery samples (17-21 mocked): 20/20 ✅
- Durable e2e subset: 29/29 ✅
- Other e2e subset: 64/65 ✅ (1 was the over-patched stub, fixed)
- Pyright: 0 new errors (4 pre-existing in generated models)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rver-responses-spec016

# Conflicts:
#	sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_local_provider.py
#	sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/durable/_manager.py
#	sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/storage_paths.py
#	sdk/agentserver/azure-ai-agentserver-core/tests/durable/test_storage_paths.py
…rver-durable-agent-demo

# Conflicts:
#	sdk/agentserver/wheels/azure_ai_agentserver_core-2.0.0b7-py3-none-any.whl
#	sdk/agentserver/wheels/azure_ai_agentserver_invocations-1.0.0b6-py3-none-any.whl
…ck + CHANGELOG cleanup + correct hosted-vs-local storage framing

Four cleanups on the responses branch following audit review:

## 1. Remove Claude sample altogether
- Deleted ``samples/sample_17_durable_claude.py`` and its mocked
  recovery test ``tests/e2e/test_recovery_sample_17_mocked.py``.
- Stripped sample-17 / Claude SDK references from ``samples/README.md``,
  ``README.md``, ``docs/durable-responses-developer-guide.md``, and
  ``docs/handler-implementation-guide.md`` (kept generic "upstream
  framework" language).

## 2. Simplify steering detection in samples + tests
The previous pattern was a double-negation:

    if cancellation_signal.is_set():
        if cancellation_signal.is_set() and not context.client_cancelled and not context.shutdown.is_set():
            # steering branch

After the shutdown/cancel decoupling, the negation is no longer
necessary — shutdown does NOT fire cancellation_signal, so when the
cancel signal IS set the cause is either client-cancel or steering.
The clean test for steering is:

    if cancellation_signal.is_set() and context.pending_input_count > 0:
        # steering branch

Applied to samples 18, 20, 21 and to
``tests/e2e/test_recovery_contract.py``.

Also tightened the pre-entry / mid-stream cancel-or-shutdown checks
in samples 18, 19, 20, 21. Previously these only observed
``cancellation_signal``; with shutdown now a distinct surface, the
handlers also need ``or context.shutdown.is_set()`` so the early
return / loop break fires on shutdown too.

## 3. CHANGELOG cleanup (1.0.0b8 entry)
- Reframed away from "Breaking Changes" — durability hasn't shipped
  and no released consumer is affected.
- Reorganised as "Features Added" (durable + steerable conversations,
  ResponseContext recovery + steering surface, DurableMetadataNamespace
  Protocol, ExitForRecoverySignal alias, FileResponseStore export,
  local-dev file-backed default, AGENTSERVER_DURABLE_ROOT env var) +
  a single genuine "Breaking Changes" entry (sync handlers rejected).
- Removed dev-iteration internal details: "ephemeral=False eliminated",
  "DurableResponseOrchestrator now registers two task primitives", "the
  shutdown-mid-handler branch now calls ctx.exit_for_recovery() instead
  of raising CancelledError", and the four bug-fix entries that all
  describe in-flight dev-loop fixes for the unshipped durability work.

## 4. Correct hosted-vs-local storage framing
- ``CHANGELOG.md``: "Default response store is now file-backed" was
  misleading — that change only affects local development. In hosted
  deployments the default remains the Foundry hosted responses storage
  API, unchanged. Reframed: "Local-development default response store
  changed from in-memory to file-backed."
- ``README.md`` 404-troubleshooting note: distinguishes hosted (Foundry
  storage) from local (file-backed under AGENTSERVER_DURABLE_ROOT).
- ``docs/durable-responses-developer-guide.md`` configuration section:
  rewrote response-store + task-store bullets to lead with hosted
  behaviour (Foundry API) and then describe the local-dev default
  (file-backed). Also mentions the new ``AGENTSERVER_TASKS_BACKEND``
  operator override for forcing one provider in either env.

## Test sweep
- Unit + Conformance + Contract + Integration + Interop: 1113/1117
  GREEN (same 4 pre-existing Hypercorn streaming-disconnect baseline
  failures unrelated to these changes).
- Recovery sample tests (18, 19, 20, 21) + recovery contract: 34/34
  GREEN. Test fixtures that simulate steering pre-entry updated to
  stamp ``pending_input_count = 1``; those that simulate shutdown
  pre-entry updated to set only ``context.shutdown`` (no longer set
  ``_cancellation_signal``).
- Black formatted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tence resilience + B11/B17 client-cancel override)

Three root-cause fixes that turn the 4 pre-existing baseline failures
(non-bg streaming persistence-resilience + B11/B17 client-cancel
override) GREEN without regressing the existing 1109 inner-suite tests.

## Root cause 1 — ``state.handler_events`` missing the synthesised ``response.failed``

In ``_process_handler_events``, the non-bg streaming Phase-1 persistence
failure branch emits ``response.created`` followed by ``response.failed``
to the SSE wire and stamps ``state.bg_record.status = "failed"``. But
``state.handler_events`` only had the ``response.created`` event
appended — the synthesised ``failed_normalized`` was emitted to the
wire and yielded to the SSE iterator but never recorded in the event
list.

``_finalize_stream`` Path B then runs and rebuilds a fresh
``ResponseExecution`` from ``state.handler_events`` via
``_extract_response_snapshot_from_events``. Because the event list
only contains ``response.created``, the rebuilt record gets
``status="in_progress"`` and OVERWRITES the in-memory record we just
stamped as ``"failed"``. Subsequent GETs then see
``visible_via_get == False`` (non-bg requires a terminal status) and
return 404 — regressing the persistence-failure resilience contract
from §4.2 / §4.3.

Fix: append ``failed_normalized`` to ``state.handler_events`` right
after it's emitted to the wire, so the subsequent finalize sees a
terminal in the event list and rebuilds the record with
``status="failed"``.

## Root cause 2 — B11/B17 client-cancel override didn't apply when the handler emitted a terminal

``_persist_and_resolve_terminal`` derived the terminal status from the
handler's emitted events without checking ``context.client_cancelled``.
For non-bg streaming, the disconnect monitor sets ``client_cancelled``
+ fires the cancellation_signal. A well-behaved handler observes the
signal, breaks its work loop, and emits its own terminal (often
``response.completed`` with the partial output that fit before the
disconnect). The framework was honoring that terminal — so a client
disconnect that should have resulted in ``status=cancelled`` per B11
+ B17 instead surfaced as ``status=completed``.

The override block at line 1822 only fires for the handler-emitted-no-
terminal case (``not _has_terminal_event``). For the
handler-emitted-something-else-but-client-cancelled case, no override
ran.

Fix: in ``_persist_and_resolve_terminal``, after computing ``status``
from the events, check ``context.client_cancelled`` and — if set and
``status != "cancelled"`` — rebuild ``response_payload`` from
``_build_cancelled_response`` and force ``status = "cancelled"``.
Replace ``state.pending_terminal`` with the override event so SSE
wire emission and persistence are consistent.

## Root cause 3 — test assertion checked the wrong flag

``test_cancel__stream_disconnect_sets_handler_cancellation_signal``
asserted ``not handler_completed.is_set()``, but the handler under
test always sets ``handler_completed`` in its post-loop close-events
block — even on cancellation, because Python ``break`` from the work
loop falls through to the post-loop emit_text_done / emit_done /
response.incomplete sequence. The assertion can only ever be true if
``asyncio.CancelledError`` propagates and kills the handler mid-
execution, which is timing-dependent on Hypercorn's disconnect
detection.

The test's contract is "B17 propagates client disconnect through the
asyncio.Event surface to the handler's work loop". The right flag to
assert on is ``handler_cancelled`` — set inside the loop when the
handler observes ``cancellation_signal.is_set()``. Switched the
assertion accordingly.

## Test sweep
- Unit + Conformance + Contract + Integration + Interop: 1117/1117
  GREEN (was 1113/1117 with 4 pre-existing baseline failures).
- E2e durable + recovery (incl. durability_contract, recovery_contract,
  recovery_sample_18/19/20/21, durable_*, stream_recovery,
  cancellation_policy, shutdown_status): 133/133 GREEN, 3 skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ACKEND entries; drop "Phase 5 follow-up" internal notes

Two cleanups for the unreleased 2.0.0b7 entry:

1. Document the two public additions landed in
   ``[agentserver] core: unified storage_paths + AGENTSERVER_TASKS_BACKEND override``
   under ``Features Added``:
   - Public ``azure.ai.agentserver.core.storage_paths`` module
   - ``AGENTSERVER_TASKS_BACKEND`` operator override

2. Strip internal-iteration references ("Phase 5 final-cleanup
   follow-up PR", "SOT spec §B3") from the ``ephemeral=`` /
   ``steerable=`` / ``ctx.suspend`` deprecation bullets. The
   transitional warnings are sufficient to communicate intent
   without pointing at internal phase scheduling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…dering (close GET race window)

Two ordering tightenings on the non-bg streaming Phase-1 persistence-
failure path, from rubber-duck review of the prior commit:

1. **``_register_bg_execution`` early-stamps the full storage-error
   snapshot**, not just ``status="failed"``. The prior commit set
   ``execution.status = "failed"`` but left
   ``execution.response`` as the initial ``response.created``
   in_progress snapshot. A concurrent GET landing between that early
   stamp and the later non-bg ``_process_handler_events`` re-stamp
   (which installs the proper ``storage_error`` envelope) could
   observe ``status=failed`` with an inconsistent body. Fixed by
   installing the full ``_build_failed_response(error_code="storage_error",
   ...)`` snapshot AND the status together in the early-stamp block.

2. **``_process_handler_events`` non-bg-stream branch now appends the
   ``failed_normalized`` terminal to ``state.handler_events`` BEFORE
   emitting/yielding it**, and uses the existing
   ``_normalize_and_append`` helper for the build + validate + append
   step. The previous order was emit → yield → append, which left a
   window where a generator close immediately after the yield (e.g.
   ASGI cancellation on the client side) would leave the event list
   holding only ``response.created``; ``_finalize_stream`` Path B's
   snapshot reconstruction would then regress
   ``status="failed"`` back to ``status="in_progress"``. The validator
   also now sees the terminal event (the prior raw-append bypassed
   ``state.validator.validate_next``).

The in-memory record's snapshot + status stamp is also moved BEFORE
the wire emit/yield in the same branch, so a GET racing the post-
yield finalize observes a consistent
``status=failed error.code=storage_error`` envelope regardless of
which side wins the race.

## Test sweep
- Unit + Conformance + Contract + Integration + Interop: 1117/1117 GREEN.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
RaviPidaparthi and others added 30 commits July 2, 2026 01:47
…t storage lifetime (protocol 2.0.0)

The resilient background/streaming response body outlives the inbound HTTP
request and re-runs on cross-process crash-recovery. The x-agent-foundry-call-id
captured on the originating CreateResponse call binds the response in Foundry
storage for its ENTIRE lifetime, so every storage operation (create / update /
checkpoint / terminal / recovery) MUST replay that SAME call_id.

Previously the resilient path persisted only user_id_key and reconstructed the
platform context with call_id=None (and a dispatch-time strip forced it to None),
so post-request and recovered storage writes sent a missing/mismatched call_id
and were rejected with "does not match an active turn" (hosted T2/T3/T15).

Fix: capture ctx.call_id into ResilientResponseInput, persist it as durable task
input (_K_CALL_ID), and reconstruct PlatformContext(user_id_key, call_id) via the
single derivation site so the create-time call_id is replayed everywhere. Removed
the dispatch-time call_id strip and the synthetic streaming ctx call_id=None.

x-agent-user-id remains NOT forwarded to 1P storage (container-side partitioning
only) — the provider still forwards call_id only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ntserver-durable-agent-demo

# Conflicts:
#	sdk/agentserver/azure-ai-agentserver-responses/azure/ai/agentserver/responses/store/_foundry_provider.py
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace stale isolation-key references with the protocol 2.0.0 model:
- handler-implementation-guide: ResponseContext.isolation (IsolationContext) ->
  platform_context (PlatformContext: user_id_key + call_id).
- responses-resilience-spec: persisted-boundary table lists user_id_key + call_id
  (create-time call_id replayed on every storage call incl. recovery; user_id_key
  is per-user partition, never forwarded to storage) instead of
  user_isolation_key/chat_isolation_key.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nge #5)

The conversation chain id (== the resilient task_id) now follows the system's
IdGenerator convention instead of an opaque `resilient-resp-<32-hex>` wrapper:

  - conversation_id present  -> cchain_{partition(conversation_id)}{scope}
  - steerable, no conv_id    -> rchain_{partition(prev_resp or response)}{scope}
  - everything else (one-shot) -> the response_id verbatim

The prefix acts as the discriminator (cchain/rchain/native caresp never collide);
the embedded 18-char partition key co-locates the chain with its responses; a
deterministic 32-char alnum `scope` digest of (agent_name \x1f session_id) fills
the native entropy slot. agent_name (DNS <=63) and session_id (any <=128) are
hashed (too long/arbitrary to embed); the constrained field is placed first so
the pair encodes injectively. `task_id == conversation_chain_id` exactly (the
`resilient-resp-` wrapper and the whole-composite truncated hash are gone).

Deterministic + reconstructable on recovery (pure function of persisted inputs);
stays within the Task API charset/limit. Rides Spec 038's no-back-compat
decision — old-format tasks also lack payload.schema_version and are pruned by
the recovery scan. SOT (responses-resilience-spec.md §4.1) updated. Chain-id
unit + e2e tests updated to the native format. Responses suite green (1118 unit/
contract/conformance + 63 e2e; the 1 import-lint failure is pre-existing).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ain ids)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
….2 + diagram + walkthrough)

Update the remaining resilient-resp-<hash> task-id references to the native task_id == conversation_chain_id form (e.g. rchain_AB12...) in 4.2, the turn-1/turn-2 ASCII diagram, and the crash-recovery walkthrough. Completes the 4.1 derivation change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… (Spec 038)

Update the primitive-owned-payload list to payload.steering (core key renamed). The _responses / _framework namespaces keep their leading underscore (functional discriminators).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…k API charset on derived task_id

Code-review Finding 2 (LOW): in case 3 (non-steerable, no conversation_id) the task_id is a verbatim response_id, which may be client-supplied via x-agent-response-id and is only structurally validated upstream (not charset-validated). The core primitive's _validate_task_id permits '.'/':', which the Public Task API charset ^[a-zA-Z0-9_-]{1,128}$ rejects. Replace the length-only assert (stripped under python -O; its comment over-promised charset protection) with a real ValueError enforcing strict charset+length. Add a paired test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevents accidental commits of battery run logs/sessions (they contain endpoint URLs + session ids and are pure test output).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…es branch

The resilient-responses-agent-demo hosted sample belongs on the demo branch; only its 3 gitignored build-context wheels were accidentally force-added here during an earlier merge. Remove them (the sample's own .gitignore marks this dir 'never committed — source of truth is sdk/agentserver/wheels/'; build.sh regenerates them at deploy).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The build-context wheels dir is gitignored ('never committed — source of truth is sdk/agentserver/wheels/') but 3 were force-added. build.sh regenerates them from the central wheels at deploy time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ntserver-durable-agent-demo

# Conflicts:
#	sdk/agentserver/azure-ai-agentserver-invocations/samples/resilient-agent-demo/src/resilient-research-agent/wheels/azure_ai_agentserver_invocations-1.0.0b6-py3-none-any.whl
#	sdk/agentserver/wheels/azure_ai_agentserver_responses-1.0.0b8-py3-none-any.whl
…tion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t wheels

Same policy as the responses demo: the build-context wheels dir is gitignored ('never committed — source of truth is sdk/agentserver/wheels/') and regenerated by build.sh; 2 were force-added. Remove them for consistency.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…inals; fix crash-failed persistence

The crash-failed recovery path (_persist_crash_failed) synthesized a bare-bones
failed ResponseObject with output=[] and no agent_reference, then wrote it via
update_response. The Foundry store validates agent_reference on every write and
rejected the marker, leaving durable responses stuck in_progress after a crash /
SIGTERM (spec §3.1 Termination paths B/C).

Root-cause fix + broader convergence (the handler owns the response object; the
framework may only set status, attach error on failed, or clear output on
cancel — per the SOT behaviour contract):

- Add shared terminal-overlay helpers to models/runtime.py
  (apply_failed_terminal / apply_cancelled_terminal / resolve_failed_response /
  resolve_cancelled_response): overlay onto the handler's persisted snapshot,
  preserving agent_reference, model, output (failed "may be partial"),
  metadata, conversation, instructions, tools, usage, sampling params, etc.;
  clear completed_at; clear output + error on cancel (B11). Fall back to the
  bare-bones builder only when no snapshot ever existed (crash before
  response.created).
- Converge all ~15 terminal-construction sites in _orchestrator.py and
  _endpoint_handler.py (storage_error, handler-exception, S-015, cancel
  winddown, client-disconnect cancel, shutdown sweep, and the wire-event
  synthesizers) onto the shared helpers so no site drops handler-owned fields.
- _persist_crash_failed: reuse the already-fetched snapshot and overlay when
  present; synthesize (carrying agent_reference + model from the task input)
  only when the response is confirmed absent; never clobber a progressed
  snapshot on an unknown store-read error (confirmed-absent vs unknown, with a
  create-only fallback).
- Error-payload hygiene: the crash/shutdown ResponseError is now {code, message}
  only — dropped the internal `type` and `additionalInfo.shutdown_reason` so no
  framework-internal lifecycle detail leaks to customers. shutdown_reason stays
  internal (message selection + logs).

Updated SOT docs (responses-resilience-spec §7.2/§7.3 + diagram + C-SERVER-ERROR,
resilience-contract Path C, resilient-responses-developer-guide) and tests
(preserve-on-failed, cancel clears output, no leaked error fields). Adds
tests/unit/test_terminal_overlay.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…y + crash-failed fix

Rebuilds the checked-in azure-ai-agentserver-{core,invocations,responses}
wheels so the demo image ships the responses-branch changes (crash-failed
agent_reference persistence, terminal-overlay fidelity, error-payload
hygiene). Verified via full hosted battery on rapida-5196: 16/16 pass
(crash-recovery, cancel, steering, failed-terminal cases all green).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…a-5196)

The battery/deploy target `-can-dev` (Foundry project rapida-5196,
RG a-rapida-canada-central) was previously a local-only azd env that was
lost on branch switches and never committed — while `.azure/config.json`
already defaults to it. This tracks the env (endpoints, resource IDs, ACR
connection acr-p5i746fm6qawc, model gpt-5.4-nano) so the deploy target
survives across sessions and matches the battery's hardcoded rapida-5196.

Contains only Azure resource identifiers/endpoints (no keys/secrets),
consistent with the already-tracked resilient-responses-agent-demo env.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Hosted Agents sdk/agentserver/*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant