Repository navigation
Plan replay: repeat an approved chat plan as a personal workflow (#1550) - #1724
Merged
Paul Lizer (paullizer) merged 46 commits intoOct 9, 2026
Conversation
Default off; depends on chat orchestration and personal workflows. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…light (WIP) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Calls _authorize_execution on the current workflow, refuses a deleted workflow or changed plan, drops the duplicate capability and source checks, and reads task_results as a dict. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The workflow task loop sends plan_replay tasks to execute_plan_replay_task as the workflow's creator and never through an agent or model runner. Group workflow saves refuse replay tasks with group_not_supported. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The due-run query excludes runs marked workflow_replay and _process_run defers any that slip through, so only the owning workflow run drives, cancels and fences a replay. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ive a replay - GET/POST /api/v2/orchestration/runs/<run_id>/plan-replay preview and save a completed, approved plan as a personal repeating workflow, behind the personal workflow gates. - Chat-side run, retry, edit, revisions and cancel refuse replay runs with workflow_replay_run_managed, so a session's roles can never drive a frozen replay. - A cancelled workflow run now stops and fences its replay. - Route policy tests cover the new routes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A browser JSON round trip turns 1.0 into 1, so the save guard compares the frozen plan number-neutrally. A saved chat plan keeps the chat-workflow cadence floor whenever its schedule is saved, not only at creation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A schedule faster than the chat minimum, or another reviewed settings error, now returns its own message and code with 422 instead of the generic service-unavailable answer. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The runner imports the replay module only for a plan replay task, so other workflows keep their import graph. Saving a replay workflow re-checks every source the plan reads, and group sources fail closed when the group setting is missing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Every compiled plan declares the implicit answer deliverable, so refusing any deliverable refused every plan. Only file deliverables (which need render_file) and unknown kinds are refused now. The freeze tests cover the allowlist, each refused class, the hash, edits, per-run re-authorization and source re-checks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds the end-to-end replay test under the real headless executor with the model stubbed, plus cancel, budget and lost-lease fencing, the workflow-conversation binding and creator checks. A re-entered replay attempt now fails with the fixed replay_execution_failed code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Covers the preview disclosure, the paused personal create, hash and eligibility refusals, settings and cadence gates, shared conversations, lost group access, store failures, delete then re-create, the read-only save guard, and the chat routes that must never drive a replay run. Saving a replay workflow now uses the read-only user-settings and document seams the chat proposal create uses, so a save never repairs user settings or refreshes a profile image. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cold-import probes across five import orders (normal and optimized), static checks that the replay module never reaches the web layer and that its callers import it lazily, and scheduler checks that replays are excluded from the due query and deferred untouched if one slips through. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The run inspector reads each task's run item, but a workflow result summary
only carries {kind, result_ref} per output, so the typed replay result was
never on the item. The replay task now returns a projection whose answer is
capped at 4000 characters with a truncated flag; the runner stores it on the
replay task's run item only. The stored json result keeps the full value.
Execution ids and artifact fields are coerced to strings, and a withheld
result also withholds the projection.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A completed chat plan gets a Repeat on a schedule card that fetches the server preview on click, discloses every frozen step, the schedule, the run time zone and the date handling, and creates a paused workflow only on an explicit save. The editor shows a saved chat plan read-only with the name, schedule and alerts editable, the run inspector renders the typed replay result, and the Classic UI routes saved chat plans to V2. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Workflows without a saved plan match a golden captured on fa3422d: the task sequence (dispatch shapes and saved run items), the personal save, the group build and the history redaction. A saved plan refuses with its fixed reason while the setting is off, under halt and continue. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ay executor Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Correct the builder to durable_execution True per the approved plan. The runner numbers replay attempts from the durable unit so a restarted worker never reuses a dead attempt's orchestration identity, and marks the replay unit replay-safe. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A durable restart re-enters the replay task with the next attempt number. Before starting, the executor now settles every earlier attempt of the same task: a completed run of the same frozen plan is adopted rather than replayed, a run whose worker died is marked failed (ownership_lost) by CAS and fenced, and a run that still holds a live lease is asked to stop and fenced, failing this attempt cleanly after one lease period rather than taking the lease over. Each attempt also settles its own orchestration run when its worker has stopped without completing it (claim failure, execution error, non-completed final status, and the cancel, budget and lease-loss paths after the stop grace), since the orchestration scheduler leaves replay runs to the workflow. CAS reads use the raw ETag-bearing record. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drive the runner's real task sequence through a worker that dies mid-replay and a second worker that resumes it, so the replay-safe unit and its durable attempt numbering are both pinned. The test exposed a crash when the answer message carries no orchestration metadata; the typed result now tolerates it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Step refusals from the server already start with "Step N (label)", so the save card showed "Step 2: Step 2 (...)". The card now renders a step refusal's message as-is and prefixes only whole-plan (step 0) refusals. The browser fixture now uses server-shaped refusals and asserts exact text. Also remove the never-raised workflow_replay_managed refusal code from the server table and its V2 mirror. Refs microsoft#1550 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add the WORKFLOW_PLAN_REPLAY feature doc, the Repeat Chat Plans On A Schedule admin row in docs/admin/orchestration.md, the user guide subsection in create-a-workflow.md, the saved chat plans section in chat-controls.md, the M5C O2 status, and the v0.261.303 release notes. Claim enable_workflow_plan_replay in features.yml and regenerate the docs inventory. Part of microsoft#1550 Refs microsoft#1543 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The approved plan chose option (c): a fixed, empty-roles identity on every path combined with option (a)'s role-free allowlist. The feature doc now uses the same label. Part of microsoft#1550 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Part of microsoft#1550 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
marked this pull request as ready for review
October 8, 2026 20:15
…w-task Bring the branch up to V2 e1657ef (VERSION 0.261.304). Conflict resolution: - application/single_app/config.py: take V2's VERSION line. The final commit sets this branch's version. - docs/explanation/release_notes.md: every V2 section is kept byte-identical, including V2's own 0.261.303 section. The plan replay section sits verbatim on top; the final commit renumbers its header. - ui_tests/fixtures/orchestration/harness_entry.tsx: keep both sides (the plan replay editor and run history fixtures, plus V2's M365Cards). - docs/_data/app_surface.yml: regenerated with scripts/build_docs_inventory.py, not hand-merged. Identical to git's auto-merge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Phase 6c records which workflow_run step waits as
plan['workflow_run_waits'] = {step_id: {'version': 1, 'workflow': handle}}.
Contract normalization drops that key, so freeze_source_run now reads it
from the source run first and passes it to classify_plan_steps.
- Each enabled step the marker names gets one replay_wait_unsupported
refusal ("Step N (label) waits for a saved workflow run to finish..."),
in place of capability_not_replayable.
- A marker that names no enabled step, or can't be read, refuses the whole
plan with replay_wait_unsupported (fail closed).
- The step-level wait-kind check and the mirrored constant stay.
Tests go through freeze_source_run with 6c's stored shape: a matched step,
unmatched and unreadable markers, and absent or empty markers that change
nothing.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A saved plan-replay workflow usually runs on a schedule nobody watches, so a run that is refused (for example after an admin turns the setting off) or that fails must still reach its creator. This restores the approved plan's default alert (deviation microsoft#7 reverted). - create_plan_replay_workflow saves the workflow with alert_mode 'rules' and exactly one rule, 'Run failed': severity high, delivery notify_only (bell only, never a pop-up), scope final, condition run_status failed or completed_with_task_errors. It goes through normal alert normalization and stays editable; a re-save returns the existing workflow unchanged, so the rule is never duplicated or put back. - The V2 Save this chat plan card discloses it before saving: "If a run fails, you'll get a 'Run failed' notification in the bell. You can change this in the workflow's alerts." - Tests: the exact stored rule and idempotent re-save; a refused scheduled run driven through the production runner reaches the bell once, with the fixed replay_disabled reason, linking to the workflow's own conversation, with no request, plan or model text and without reaching the harness; the client notice's parity with the server rule name; Playwright asserts the notice. - Docs: feature doc (Default alert section, refusals, test table), user guide, admin row, chat-controls reference and release notes. Part of microsoft#1550 Refs microsoft#1543 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The coordinator assigned 0.261.305 after V2 moved to 0.261.304. This sets config.py to 0.261.305 and renumbers only this branch's own 0.261.303 references: the feature, M5C, admin, guide and chat-controls docs, the top release-notes section, and the plan-replay test headers and version floors. V2's own (v0.261.303) release-notes section is unchanged. Part of microsoft#1550 Refs microsoft#1543 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 moved past e1657ef before the push. Merged with rerere off; no conflicts. config.py carries the same 0.261.305 line on both sides, and functions_saved_analysis.py merges independent hunks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 reached 0.261.305 with microsoft#1726 (817f9dd) before the push, so this branch takes V2 tip+1. Renumbers only this branch's own 0.261.305 references (the same 30 lines in 15 files that 90947b2 set); microsoft#1726's own 0.261.305 references are untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…o the plan-replay branch Merged with rerere off. The only conflict was docs/explanation/release_notes.md: V2's file is kept byte-identical and this PR's section sits on top of it as a pure insertion. chat-controls.md and the two route-policy tests auto-merged with independent hunks. config.py merged cleanly because both sides are at 0.261.306; this PR's references move to 0.261.307 in the following commit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 587094f is at 0.261.306, so this PR takes the next version. Only this PR's own references move from 0.261.306 to 0.261.307: the same 30 lines in 15 files that the previous renumber touched. V2's sections and headers are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Approved light catch-up after the full .307 validation and mutation table. The shared route-policy inventory merged independent catalogue and replay additions. Keep V2's .306 config line in this merge so the separate last commit restores tip+1 (.307); no other replay references change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 11d8478 remains at .306, so tip+1 is still .307. Keep the application version in its own last commit; all plan-replay test and documentation references already use .307. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Approved light catch-up after full replay validation. MessageList, chat-controls and the shared harness fixture merged independent pending-action and plan-replay additions. Both sides retain V2's .307 version; replay takes tip+1 (.308) in a separate last commit. No rebase or force-push. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The V2 pending-action cards now read the conversation's saved-action list. Handle that exact GET through the shared fixture before tracking replay requests, while retaining strict rejection of all unknown browser requests. The replay card suite is again 7/7 after the V2 merge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 08def99 reached .307 before the push, so replay takes tip+1 (.308). Renumber only replay's own 30 references in 15 files. Keep every V2 version reference and release-notes section intact. This is the separate last commit after the merge and UI-fixture compatibility fix. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
15 of 24 tasks
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
merged commit Oct 9, 2026
db89800
into
microsoft:paullizer-react-v2-ui
11 checks passed
Paul Lizer (paullizer)
added a commit
that referenced
this pull request
Oct 9, 2026
Plan replay (#1724) took 0.261.306 after V2 moved to 0.261.305. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Repeat a chat plan on a schedule (plan replay, M5C O2). A finished orchestrated answer in V2 chat offers Repeat on a schedule. The card freezes the plan that just ran, discloses every frozen step (title and capability), the request, time handling, cadence floor, step cap and who it runs as, and saves a paused personal workflow with one
plan_replaytask only after the user clicks Save workflow.Each run replays the frozen plan as its creator (
workflow.user_id), in the workflow's own conversation, through the headlessHarnessExecutionunder the durable workflow lease. The model never adds, removes or rewrites steps; the plan is SHA-256 hashed at save and checked again before every run, and the creator, setting, capabilities and every source the plan reads are re-authorized on every run.Deny by default. Only seven role-free, single-pass capabilities can replay (
plan-replay-allowlist-v1). Web search, page fetch, deep research, actions, agents, workflow steps, async waits (including Phase 6c'ssaved_workflow_run), file deliverables and plans that asked the user a question are refused with a fixed reason per step.Admin:
enable_workflow_plan_replay(Chat Orchestration pane), default off, requiresenable_chat_orchestrationandallow_user_workflows. With it off, behaviour is identical to today (off-golden test); an existing replay workflow fails with a fixed reason instead of crashing or skipping.Linked issue
Part of #1550
Refs #1543
Release Notes & Latest Features
New Feature
Bug Fix
UI Enhancement
Breaking Change
Internal only
Is this visible to end users?
Yes
No
Is this admin-facing (Admin Settings, governance, deployment, config)?
Yes
No
Should this become a Latest Feature card?
Yes
No
Already added
Screenshot needed for the card?
Yes
No
Attached
Version bump
application/single_app/config.pyVERSIONthird segment bumped, or not needed because this is docs-only0.261.307; replay is0.261.308(version commit9c18dbfed), followed by cleanup commit8d3955680; Phase 6c remains0.261.309.deployers/version.txtbumped, or not needed becausedeployers/was not changedTesting / validation
Setup. Every Python run used the pinned venv (
...\files\venv\Scripts\python.exe) withPYTHONPATH=application/single_app;functional_tests,PYTHONIOENCODING=utf-8andPYTHONUTF8=1. pytest also got-p no:langsmith_plugin -p no:cacheprovider. Runs were one process at a time, on the merged tree at head964c5c5a0with VERSION0.261.307.Validation heads. The full four-mode and mutation evidence below is from
964c5c5a0(VERSION0.261.307); the approved light catch-ups are at3bd32b057and9c18dbfed(VERSION0.261.308). Current PR head8d3955680adds the reviewed CodeQL-quality cleanup without changing the version. On that head, execution + freeze + replay-card tests passed 129 normally; execution + freeze passed 122 under optimized Python.New Python tests, four ways each. Every run passed. Since the first push, Fix A and Fix B took the freeze file from 79 to 87 cases and the routes file from 29 to 31.
| File | Tests | pytest | pytest
-O| script |-Oscript || --- | --- | --- | --- | --- | --- |
|
functional_tests/test_workflow_plan_replay_freeze.py| 87 | ✅ | ✅ | ✅ | ✅ ||
functional_tests/test_workflow_plan_replay_execution.py| 27 | ✅ | ✅ | ✅ | ✅ ||
functional_tests/test_workflow_plan_replay_routes.py| 31 | ✅ | ✅ | ✅ | ✅ ||
functional_tests/test_workflow_plan_replay_boundaries.py| 15 | ✅ | ✅ | ✅ | ✅ ||
functional_tests/test_workflow_plan_replay_off_golden.py| 9 | ✅ | ✅ | ✅ | ✅ |Each full file path above was substituted for
<file>in all four commands below. All 20 commands exited 0 (169 checks per mode):V2 merges since the first push touch code that replay runs through:
Microsoft 365 files, emails and events as first-class citations #1718 changed
HarnessExecution's finish path, which every replay uses.Fix HNS result cleanup and workflow deletion recovery #1726 changed the workflow result store, the delete and cancel routes, and two mixed-source telemetry names. The replay module's one mixed-source call,
resolve_authorized_source_manifest, is unchanged.The latest merge also brings #1728's composition-to-M365-citations import edge and #1730's conversation-deletion recovery. All five rows above, including execution and boundaries, were run on the tree with every merge included.
Route policy (venv pytest). All passed.
route_tests/test_route_blueprint_policy_inventory.py: 15route_tests/test_route_unauthenticated_policy_contract.py: 11route_tests/test_route_policy_test_coverage.py: 3V2
node --test functional_tests/test_v2_workflow_plan_replay_client.mjs(Node): 9/9 pass. The new one checks that the card names the same alert rule the server saves.node --test functional_tests/test_v2_workflow_run_action_clients.mjs(Node, changed by Fix HNS result cleanup and workflow deletion recovery #1726): 14/14 pass.npm --prefix .\application\v2_ui run typecheck: pass.npm --prefix .\application\v2_ui run build: pass. The chunk-size warning was already there.Playwright (venv pytest):
ui_tests/test_v2_workflow_plan_replay_card.py: 7 passed. It uses hostile step titles and workflow names, and asserts the card's one-line alert disclosure.ui_tests/test_workflow_classic_advanced_guard.py: 22 passed.Neighbours (venv pytest, one file at a time). 37 of 44 files passed; the seven failing files match base exactly, as detailed below. Each Python file listed here and under Route policy used
python -W ignore::DeprecationWarning -m pytest -p no:langsmith_plugin -p no:cacheprovider -q <file>. Failed files were also compared on base and head with--tb=line -rf, including exact test names and error messages.test_orchestration_actions_admin.py: 8, plus 14 subtestsWorkflow proposals:
test_orchestration_workflow_proposal_routes.py: 60test_orchestration_workflow_propose_capability.py: 89test_orchestration_workflow_proposals_admin.py: 12test_orchestration_workflow_proposal_editor_round_trip.py: 5test_orchestration_workflow_proposal_done_when.py: 2test_orchestration_workflow_proposal_imports.py: 6Harness:
test_orchestration_harness_execution.py: 649, plus 2 xfailedtest_orchestration_harness_scheduler.py: 213test_orchestration_harness_routes.py: 157test_orchestration_harness_chat_checks.py: 6V2 tests added or changed since the first push's base
fa3422dee:test_orchestration_terminal_wait_retry.py: 8 (new)test_orchestration_settings_runtime_state_binding.py: 16 (new)test_orchestration_m365_approval_retry_flow.py: 8 (new)test_orchestration_file_sign_in_publication.py: 5 (new)test_orchestration_output_lifecycle.py: 225 (changed)test_v2_workflow_run_tracking_xss_guardrail.py: 6 (changed)test_workflow_delete_recovery_routes.py: 43 (new in Fix HNS result cleanup and workflow deletion recovery #1726)test_workflow_result_store.py: 45, plus 112 subtests (changed in Fix HNS result cleanup and workflow deletion recovery #1726)test_saved_analysis_service.py: 16 (changed in Fix HNS result cleanup and workflow deletion recovery #1726)Latest V2 merge and shared composition/import neighbours:
test_m365_source_citations.py: 42test_m365_mail_calendar_search.py: 17test_orchestration_dependency_imports.py: 11test_orchestration_compose_output_retry.py: 23test_conversation_delete_hns_directory_cleanup.py: 42test_conversations_read_ownership_authorization.py: 10test_v2_scope_and_bootstrap_timings.py: 13Workflow runner and validation:
test_workflow_task_sequence.py: 10test_workflow_definition_store_integration.py: 7test_workflow_run_time_context.py: 13test_workflow_merge_task.py: 54test_group_workflow_round_trip_preservation.py: 5Settings:
test_admin_settings_field_contract.py: 2test_admin_settings_fields_registry_integrity.py: 5Base failures. Each of the seven files fails identically on base
587094fb6, compared in a temporary detached worktree: the same tests, with the same failure messages. The cancellation and hardening files came from #1726; the manifest-contract suite was added to this round's shared-source neighbours.| File | Fails on both base and head | Notes |
| --- | --- | --- |
|
test_workflow_task_document_actions.py|test_document_run_items_are_task_scoped,test_task_document_action_failures_stay_inside_the_retry_loop| The other 14 pass ||
test_personal_workflows_feature.py|test_personal_workflows_feature_wiring| ||
test_generic_workflow_automation.py|test_generic_workflow_ui_defaults_to_no_document_action,test_generic_workflow_runner_retains_direct_execution_paths| AssertsVERSION = "0.250.065"and a string that's absent from both base and head ||
test_group_workflows_feature.py|test_group_workflow_feature_contracts| 34/41 checks pass on both. Standalone execution also reports 34/41 and exits 1 on both. ||
test_workflow_cancellation.py|test_runner_cancellation_helpers_cover_personal_and_group_items,test_runner_returns_cancelled_terminal_state_and_clears_active_run,test_m365_authorization_denial_finishes_the_run_instead_of_waiting_again| Under pytest,NameErrorforcurrent_workflow_executionandcapture_execution_identityinside the runner on both; the other 5 pass. As a script (python functional_tests/test_workflow_cancellation.py) it exits 0 on both. ||
test_mixed_source_hardening.py| 7 of 21:test_handoff_exposes_the_same_bounded_envelopes_used_for_synthesis,test_compare_failed_source_is_fatal_and_failed_target_does_not_stop_later_targets,test_manifest_and_tabular_cancellation_stop_work_without_failure_downgrade,test_narrative_comparison_and_reduction_cancellation_stop_later_work,test_foundry_context_opt_out_filters_mixed_evidence_for_all_runtime_shapes,test_mixed_analyze_forwards_observed_native_token_usage,test_analyze_all_action_is_analyze_only_and_manifest_is_fresh| The same 7 messages on both, starting with a CosmosUnrecognized credential type(no Cosmos key in this environment). As a script it stops at the same Cosmos error on both. ||
test_mixed_source_manifest_contracts.py| 9 of 15:test_mixed_classification_order_and_partition,test_xsd_summary_evidence_preserves_schema_source_kind,test_duplicates_and_cross_scope_filename_identity,test_unresolved_and_unsupported_do_not_erase_valid_sources,test_personal_group_public_and_chat_authorization,test_phase_2_evidence_execution_and_partial_coverage,test_restrictive_manifest_scope_fails_closed_and_preserves_storage_locator,test_batch_authorization_snapshot_and_source_limit,test_manifest_diagnostics_are_aggregate_only| The same 9 names and messages on both, including missing Cosmos credentials, held-document errors and manifest assertions; the other 6 pass. |Docs (venv python, through
__main__)python .\scripts\build_docs_inventory.pyleaves the committeddocs/_data/app_surface.ymlunchanged. The file was regenerated during the first merge, not hand-merged, and the subsequent merges didn't touch it. Against587094fb6it addsenable_workflow_plan_replay, taking the capabilities from 129 to 130.test_docs_app_surface_coverage.py: 7/7.test_docs_site_quality.py: 6/6.test_docs_link_integrity.py: 2/5, the same as base587094fb6. Both have the same 11 broken relative links and 23 site links that don't resolve. This PR's 7 new relative links and 2 new site links all resolve. It also declares one screenshot slot,reference/chat-controls-saved-chat-plan-card.png, which shows a placeholder until the image exists.test_docs_release_notes_integrity.py: 0/1, the same as base. The generated release-notes pages are stale on base too (234 releases missing); the only new missing release is this PR'sv0.261.308(autonomous decision 15).Approved light catch-ups (no four-mode or mutation restart)
| Head | Catch-up | Result |
| --- | --- | --- |
|
3bd32b057(.307) | #1729; only the route-policy inventory was touched on both sides | Five replay suites plus all three route-policy suites: 198 passed. Docs coverage 7/7, quality 6/6, all six guardrails passed, clean tree. ||
9c18dbfed(.308) | #1731; MessageList, shared harness fixture, chat controls and config | Five replay suites, three route-policy suites, replay card, M365 pending-action cards and Classic guard: 237 passed. Typecheck and build passed; docs coverage 7/7, quality 6/6; all six guardrails passed; clean tree. |The second catch-up used the same pytest command and flags above, with these exact targets in one invocation:
The UI rows are replay card 7, pending-action cards 10 and Classic guard 22.
npm --prefix .\application\v2_ui run typecheckandnpm --prefix .\application\v2_ui run buildboth exited 0 on the final head.The initial replay-card probe correctly caught V2's new
GET /api/msgraph/pending-actionsas an unexpected request. Commit433788790reused V2's shared empty-list fixture before tracking replay requests; the probe then passed 7/7 and the final combined run passed. Unknown requests remain strict failures, and Repeat on a schedule still performs no replay request until clicked.The inventory did not need another merge because neither catch-up changed
docs/_data; coverage confirmed it is still current. The integrity failures above are the full-run results, not claims that the two integrity scripts were rerun in the light catch-ups. The replay release-notes header was subsequently renumbered to.308.Mutation testing. 54/54 mutations were killed; every revert left
git diff --quiet -- applicationclean. M1–M15 ran on026c7f82e, M16 on1d544fb21, and fixes M17–M18 on the later replay tree.Mutation kill table
| ID | Mutation | Failing tests (file::test) | Killed | Clean after revert |
| --- | --- | --- | --- | --- |
| M1a | Run as a non-creator: the actor's id drives the replay and the explicit actor check is gone |
replay_execution::test_only_the_creator_runs_the_replay| yes | yes || M1b | Creator check accepts any recorded creator |
replay_freeze::test_only_the_creator_is_ever_the_actor,replay_execution::test_only_the_creator_runs_the_replay| yes | yes || M2a | Skip the per-run re-authorization (context built from the task) |
replay_execution::test_a_deleted_or_changed_workflow_stops_before_step_one,replay_off_golden::test_a_saved_plan_refuses_with_its_fixed_reason_while_off[halt]| yes | yes || M2b | Skip the run-time source re-check |
replay_freeze::test_every_source_is_rechecked_as_the_creator| yes | yes || M2c | Skip the run-time capability re-check |
replay_freeze::test_a_capability_the_admin_removed_or_turned_off_is_refused,replay_execution::test_a_deleted_or_changed_workflow_stops_before_step_one| yes | yes || M2d | Skip re-reading the stored workflow before step 1 |
replay_execution::test_a_deleted_or_changed_workflow_stops_before_step_one| yes | yes || M3a | Accept a hash mismatch at run time |
replay_freeze::test_the_stored_task_runs_only_while_its_hash_is_unchanged| yes | yes || M3b | Accept a stored plan whose hash changed after admission |
replay_execution::test_a_deleted_or_changed_workflow_stops_before_step_one| yes | yes || M3c | Accept a save without the previewed hash |
replay_routes::test_saving_needs_the_previewed_hash[None]| yes | yes || M3d | Let an ordinary save edit the frozen plan |
replay_routes::test_the_frozen_plan_stays_read_only_through_the_ordinary_save,replay_freeze::test_any_edit_to_the_frozen_plan_through_a_save_is_refused| yes | yes || M4a | Allow deep_research |
replay_freeze::test_each_refused_class_has_its_fixed_reason[deep_research-arguments6-capability_not_replayable-can't| yes | yes || M4b | Allow the external-effect and workflow capabilities |
replay_freeze::test_each_refused_class_has_its_fixed_reason[workflow_run-arguments1-capability_not_replayable-can't| yes | yes || M4c | Allow the Phase 6c saved_workflow_run wait |
replay_freeze::test_every_refused_wait_kind_is_refused[saved_workflow_run]| yes | yes || M4d | Allow the role-gated web_search |
replay_freeze::test_each_refused_class_has_its_fixed_reason[web_search-arguments10-role_required-needs| yes | yes || M4e | Remove the step budget |
replay_freeze::test_the_step_budget_and_an_empty_plan| yes | yes || M4f | Remove the run-time budget |
replay_execution::test_an_exhausted_budget_cancels_and_fences_the_replay| yes | yes || M5a | Allow elicitation at freeze |
replay_freeze::test_freeze_refuses_runs_it_must_not_repeat[changes10-elicitation_not_replayable],replay_routes::test_a_run_that_must_not_repeat_cannot_be_saved[changes3-elicitation_not_replayable]| yes | yes || M5b | Allow elicitation in a stored plan at run time |
replay_freeze::test_a_stored_plan_that_asks_a_question_is_refused_before_step_one| yes | yes || M6a | Run in the original chat |
replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation| yes | yes || M6b | Accept the source chat as the replay conversation |
replay_execution::test_the_source_chat_is_never_the_replay_conversation| yes | yes || M6c | Accept a conversation that is not a workflow conversation |
replay_execution::test_only_the_private_workflow_conversation_is_accepted[changes0]| yes | yes || M7a | Widen roles in the harness identity |
replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation| yes | yes || M7b | Widen roles in the run-time capability check |
replay_freeze::test_manual_and_scheduled_runs_are_allowed_the_same_capabilities| yes | yes || M8a | Let the model rewrite a step's instruction |
replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation| yes | yes || M8b | Let the model drop a step |
replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation| yes | yes || M8c | Resolve the run-time line in UTC instead of the schedule's time zone |
replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation| yes | yes || M9a | Skip the setting gate |
replay_off_golden::test_a_saved_plan_refuses_with_its_fixed_reason_while_off[halt],replay_freeze::test_every_run_rechecks_the_settings[enable_workflow_plan_replay-replay_disabled]| yes | yes || M9b | Off path: every run item gets a plan_replay projection |
replay_off_golden::test_workflows_without_a_saved_plan_match_the_base[task_sequence]| yes | yes || M9c | Off path: the setting defaults on |
replay_off_golden::test_the_setting_is_off_by_default_and_saves_only_a_real_true| yes | yes || M9d | Off path: the setting saves any truthy value |
replay_off_golden::test_the_setting_is_off_by_default_and_saves_only_a_real_true| yes | yes || M9e | Off path: every task takes the replay branch |
replay_off_golden::test_workflows_without_a_saved_plan_match_the_base[task_sequence]| yes | yes || M10 | Show the typed result when the task output is withheld |
replay_execution::test_a_withheld_result_also_withholds_the_typed_projection[False]| yes | yes || M11a | Create a group replay workflow |
replay_freeze::test_group_workflows_are_refused_at_creation| yes | yes || M11b | Run a group replay workflow |
replay_freeze::test_group_workflows_are_refused_at_run_time[changes0],replay_execution::test_only_the_creator_runs_the_replay| yes | yes || M12a | Skip the cadence floor |
replay_routes::test_a_schedule_faster_than_the_chat_minimum_is_refused| yes | yes || M12b | Skip the cadence floor when a replay workflow is re-saved |
replay_routes::test_the_frozen_plan_stays_read_only_through_the_ordinary_save| yes | yes || M13a | The orchestration scheduler's due-run query picks up replays |
replay_boundaries::test_the_due_run_query_excludes_workflow_replays| yes | yes || M13b | The orchestration scheduler drives a replay it reaches |
replay_boundaries::test_a_replay_that_reaches_the_scheduler_is_deferred_untouched| yes | yes || M14a | The builder saves a replay workflow that is not durable |
replay_routes::test_saving_creates_one_paused_personal_workflow_that_repeats_the_frozen_plan| yes | yes || M14b | Skip reconciling earlier attempts |
replay_execution::test_a_durable_restart_settles_the_dead_attempt_and_replays_once| yes | yes || M14c | Repeat a completed earlier attempt instead of adopting it |
replay_execution::test_a_completed_prior_attempt_is_adopted_not_repeated| yes | yes || M14d | Adopt a completed earlier attempt of another frozen plan |
replay_execution::test_a_completed_prior_attempt_of_another_plan_is_refused| yes | yes || M14e | Stop a live earlier attempt without fencing it |
replay_execution::test_a_live_prior_attempt_is_stopped_and_fenced_but_never_taken_over| yes | yes || M14f | Leave a settled earlier attempt unfenced |
replay_execution::test_a_durable_restart_settles_the_dead_attempt_and_replays_once| yes | yes || M14g | Treat a dead earlier attempt as settled without settling its run |
replay_execution::test_a_durable_restart_settles_the_dead_attempt_and_replays_once| yes | yes || M14h | A stopped attempt never settles its own run |
replay_execution::test_an_interrupted_attempt_settles_its_own_run| yes | yes || M14i | A stopped attempt cannot settle a run its own lease still holds |
replay_execution::test_an_interrupted_attempt_settles_its_own_run| yes | yes || M15a | The runner's replay unit is not replay-safe |
replay_execution::test_the_runner_replays_one_durable_unit_numbered_by_its_own_attempt| yes | yes || M15b | The runner numbers a durable replay by its in-process retry index |
replay_execution::test_the_runner_replays_one_durable_unit_numbered_by_its_own_attempt| yes | yes || M15c | The run item drops the typed replay result |
replay_execution::test_the_runner_replays_one_durable_unit_numbered_by_its_own_attempt| yes | yes || M16 | Card shows a doubled step prefix (
Step 1: Step 1 (...)) |ui_tests/test_v2_workflow_plan_replay_card.py::test_ineligible_preview_shows_refusals_without_save_form| yes | yes || M17 | Fix B: drop the default 'Run failed' alert rule |
replay_routes::test_a_saved_replay_workflow_starts_with_one_editable_run_failed_alert,replay_routes::test_a_refused_replay_run_reaches_the_bell_with_its_fixed_reason| yes | yes || M18a | Fix A: ignore Phase 6c's stored wait marker when freezing |
replay_freeze::test_a_waited_workflow_run_step_is_refused_as_a_wait_when_frozen| yes | yes || M18b | Fix A: let a wait marker that names no running step through |
replay_freeze::test_a_wait_marker_that_matches_no_running_step_refuses_the_whole_plan[marker0]| yes | yes |Guardrails
I ran the coordinator's guardrails script on the full-validation tree at
964c5c5a0and again on both light catch-ups. The final run is at9c18dbfed, with the venv python, against V208def99f7:run_v2_guardrails.py <worktree> 08def99f7a39e20376bee354db8b7fc838a9ae0e HEAD replayIt exits 0. It reviewed 41 changed files: 12 app
.py, 21 on the XSS surface, 12 route.pyand 7 V2 ts/tsx.| Check | Result |
| --- | --- |
| broken-access-control | PASS (12 files) |
| xss-sinks | PASS (21 files) |
| swagger-routes | PASS (2 changed route files) |
| python-syntax | PASS (543 files compile) |
| malicious-pr-review | PASS. 1234 marker findings, 0 blockers; verdict "Needs investigation". |
| v2-ts-sinks (added lines) | PASS (no sink patterns in 7 ts/tsx files) |
The malicious-PR review flags changed lines by keyword marker, for example "AI, plugin, agent or workspace boundary" and "security control". Most findings are in the replay module and its tests, which deal with authorization and refusals by design. The full run had 1231 marker findings and zero blockers; the final run adds three markers for the closed-API fixture's empty pending-action list. These are review markers, not 1234 confirmed vulnerabilities.
The earlier 17 "dynamic execution, persistence, or system access" findings were reviewed: lines containing "empty" in prose/tests, plus
subprocessorexecFileSyncin the fresh-process import probe, the off-golden fixture writer (git rev-parse HEAD, only with--write-golden) and the V2 client's AST-only constants/registry read. The final fixture uses only the shared local test helper and never broadens the unknown-request fallback.Documentation
Release notes updated, or not needed
v0.261.308section; prior V2 release-note sections are preserved.Feature documentation updated, or not needed
docs/explanation/features/WORKFLOW_PLAN_REPLAY.md; O2 status inWORKFLOW_M5C_COMPLETION_AND_NEXT_STEPS.md)Fix documentation updated, or not needed
Also:
docs/admin/orchestration.mdsettings row (the toggle lives in the Chat Orchestration pane, so the row sits with the other orchestration limits it depends on),docs/_data/features.ymlkey claim, regenerateddocs/_data/app_surface.yml,docs/guides/create-a-workflow.md("Repeat a chat plan on a schedule") anddocs/reference/chat-controls.md("Saved chat plans (V2 interface)").Security checklist
New Flask routes include
@swagger_route(security=get_auth_security())GET/POST /api/v2/orchestration/runs/<run_id>/plan-replay, with@login_required,@user_required,@enabled_required('allow_user_workflows')and@workflow_user_required; the replay setting and Chat Orchestration are checked inside, with fixed refusals; route-policy inventory and unauthenticated contract updated)Settings sent to non-admin frontends use
sanitize_settings_for_user()Browser JavaScript is served from local SimpleChat static assets only; no CDN-hosted JS
No secrets, keys, connection strings, or local-only artifacts are included
Decisions (coordinator decisions 1–8)
Roles: option (c), a fixed empty-roles identity plus option (a)'s role-free allowlist. Freeze, every run and the harness identity use
user_roles=[]anduser_enable_agents=False, so a manual run and a scheduled run see the same capabilities: neither consults the trigger's roles. The only role-gated capability on the registry,web_search, is refused at freeze asrole_required. Nothing is ever widened, and there's no stale snapshot to expire. A server-side role snapshot (option b) is a follow-up.Replayable list (
plan-replay-allowlist-v1), deny by default.| Capability | Why it can repeat |
| --- | --- |
|
document_search| Searches documents the creator can still open; access is re-checked before every run. ||
document_analyze| Analyzes documents the creator can still open, with the frozen instructions. ||
document_compare| Compares documents the creator can still open, with the frozen instructions. ||
document_merge| Merges documents the creator can still open into a file kept with the run. ||
tabular_inspect| Reads the shape of tabular files the creator can still open; it finishes in the step. ||
compose| Writes the answer from earlier step results with the frozen instructions. ||
generate_image| Creates a new image from the frozen prompt; reference images are refused. |Everything else is refused:
workflow_*and the external-effect and approval-floor capabilities (capability_not_replayable),web_search(role_required),tabular_analyze,render_file, file deliverables and every async wait kind includingsaved_workflow_run(replay_wait_unsupported),url_fetch,deep_research,tabular_mergeand anything unlisted (capability_not_replayable), elicitation (elicitation_not_replayable), and more than 8 steps (replay_budget_exceeded).Creation path: (B), a "Repeat on a schedule" action on the completed plan's answer. It's deterministic (no model chooses what is frozen), freezes exactly the run the user saw, and needs no planner change. The card fetches a server-built preview (
GET .../plan-replay?conversation_id=) only on click and discloses the request, time handling, cadence floor, step cap, "Runs as", every frozen step (N. title+ capability label) and any per-step refusal. Save workflow is the manual approval; thePOSTre-freezes, re-checks sources and requires the previewedplan_sha256. The workflow is created paused unless the user turns the schedule on.Execution: reuse
HarnessExecutionthrough a thin adapter. Each attempt creates a new orchestration run in the workflow's conversation, with ids derived from (workflow run, task, attempt) and aworkflow_replaymarker. The durable workflow unit numbers attempts across worker restarts (max 3), so a resumed attempt never reuses a dead worker's identity. The orchestration scheduler excludesworkflow_replayruns (due-run query and per-run guard), so only the workflow runner drives them. Before a new attempt, earlier attempts are reconciled: a completed one with the same hash is adopted, a dead one is settled asownership_lostand fenced, and a still-live one is stopped and fenced or the attempt fails after ~55 s. The workflow lease is checked through the runner's cancel check on every step, and each attempt self-settles through a CAS.Relative time: frozen as written, plus a server-built run-time line ("current local time") in the schedule's time zone (then the frozen chat time zone, then UTC). The card's Time row discloses it. Waits and budgets: steps that wait asynchronously are refused in this slice; a run is limited to 8 steps and 900 s, with a 30 s stop grace.
Stored shape: task
type: plan_replaywithplan_replay = {version: 1, allowlist_version, request, frozen_plan, frozen_seeds, plan_sha256, approval: {approved_by, approved_at, plan_sha256}, provenance: {source_run_id, source_conversation_id, created_by, frozen_at, time_handling, time_zone}}. The hash covers{task_version, allowlist_version, request, plan, seeds}.tasksis part ofWORKFLOW_DEFINITION_FIELDS, so the definition revision changes with any edit; the ordinary save refuses changes to the frozen plan (plan_replay_read_only) and keeps every non-editable key at its stored value.Refused at run time: the run fails before step 1 with the fixed reason (see Refusal mapping); it's not retried. The run inspector shows the reason as the task and run error. Alerts follow the workflow's own alert settings, as for any failed run; when an alert fires, its body carries the same fixed reason. Nothing new goes to the bell.
Setting off / capability removed: existing replay workflows stay saved and editable (name, schedule, alerts) but every run fails with
replay_disabled,orchestration_disabled,personal_workflows_disabledorcapability_unavailable. Turning the setting or capability back on lets the next run proceed; nothing is deleted or rewritten.Refusal mapping
| Code | Where | Card | Editor | Run inspector / alerts |
| --- | --- | --- | --- | --- |
|
replay_disabled| save, run | Button hidden (feature gate); a stale save shows the fixed text inline | Unchanged | Run fails: "Repeating chat plans is turned off. Ask an admin to turn it on, then run the workflow again." ||
orchestration_disabled/personal_workflows_disabled| save, run | Button hidden; inline text on a stale save | Unchanged | Run fails with the fixed text ||
group_not_supported| group save | — | Group save refused with "Only personal workflows can repeat a chat plan." | — ||
creator_mismatch| run | — | — | Run fails: "This saved plan can only run as the person who created it." ||
plan_hash_mismatch| save, run | Inline alert + Reload plan | — | Run fails: "This saved plan changed after it was approved. Create it again from chat." ||
allowlist_version_unsupported| run | — | — | Run fails with the fixed text ||
capability_not_replayable,role_required,replay_wait_unsupported,elicitation_not_replayable,replay_budget_exceeded(step cap) | preview, save, run | Preview lists each as "Step N (label) …" or "Whole plan: …" under "This plan has replay notes:" and shows no save form | — | Run fails with the code's fixed text ||
capability_unavailable| save, run | Inline fixed text | — | Run fails: "A step in this plan is turned off or no longer available." ||
source_unavailable| save, run | Inline fixed text | — | Run fails: "A document, group or public workspace this plan reads is no longer available to you." ||
source_run_not_eligible,shared_conversation_not_allowed,conversation_context_not_replayable| preview, save | Preview note / inline fixed text | — | — ||
replay_budget_exceeded(time) | run | — | — | Run fails: "The repeated plan took longer than its time limit and was stopped." ||
replay_execution_failed| run | — | — | Run fails: "The repeated plan could not finish. Open the run for details." ||
workflow_unavailable| run | — | — | Run fails: "This workflow was deleted or is being deleted, so the saved plan didn't run." ||
plan_replay_read_only| edit | — | Frozen steps are shown read-only; a save that changes the frozen plan, the task or its type is refused with "A saved plan can't be edited. Create it again from chat." Classic shows a toast and routes editing to V2 | — ||
workflow_replay_run_managed| chat run/retry/edit/revisions/cancel | — | — | 409 from the chat routes: "This plan runs as part of a saved workflow. Open the workflow to run or cancel it." ||
quota_exceeded,model_unavailable,workflow_conflict,run_not_found,invalid_request,service_unavailable| save | Inline fixed text | — | — |The client mirrors the server's
REFUSAL_MESSAGESexactly (test_v2_workflow_plan_replay_client.mjsasserts equality), so model prose never reaches these surfaces.Decisions made autonomously
Each of these is the conservative, reversible option.
Setting name. I kept the suggested
enable_workflow_plan_replay.Roles: option (c). A fixed, empty-roles identity on every path, plus option (a)'s role-free allowlist. A role snapshot (option b) is a follow-up.
Creation path (B). "Repeat on a schedule" sits on a completed plan's answer. It's deterministic, freezes exactly the run the user saw, and needs no planner change.
The admin row is in
docs/admin/orchestration.md. The toggle sits in the Chat Orchestration pane, next to the cadence floor and workflow quota it depends on.Waits are refused in this slice. That covers
tabular_analyze,render_file, file deliverables and every async wait kind, including Phase 6c'ssaved_workflow_run.deep_research,url_fetchandtabular_mergeare refused too.Workflows are kept, not disabled or deleted, when the setting is turned off or a capability is removed. Every run fails with the fixed reason until the setting or capability is back.
The default "Run failed" alert is restored per the approved plan (Fix B,
9f87bf1c4). A new replay workflow starts with one editable high-priority,notify_onlyrule forfailedorcompleted_with_task_errors, disclosed on the card and in the documentation. The stable rule is created server-side, never re-added when a creator edits/removes it, and the tests and mutation M17 cover its behavior.alert_priorityremainsnone, so this does not create a popup for every run.Creation is idempotent per (source run, plan hash). Saving the same plan twice returns the existing workflow with a 200 instead of a 201.
Only the workflow manages a replay run.
The chat run, retry, edit, revisions and cancel routes return 409
workflow_replay_run_managedfor a replay orchestration run.A replay run can't be frozen again; that's refused as
source_run_not_eligible.Budgets. At most 8 steps and 900 s per run, with a 30 s stop grace.
The workflow conversation gets both the orchestration answer and the workflow's normal result message. This changed from the approved plan. The plan made the harness answer the only visible answer, with a pointer message. Collapsing them is a follow-up.
Status codes.
replay_disabledandcreator_mismatchare 403,plan_hash_mismatchis 409 andquota_exceededis 429. Every code maps to one of 403, 404, 409, 422, 429 or 503.The Phase 6c constant is mirrored locally as
SAVED_WORKFLOW_RUN_WAIT_KIND = 'saved_workflow_run', not imported, so this PR doesn't depend on 6c landing first. A test refuses it by name.Card refusal text.
Step refusals render as the server writes them, "Step N (label) …". Only whole-plan refusals get a "Whole plan: " prefix.
I removed the unused
workflow_replay_managedcode from the server table and its V2 mirror.The release-notes pages weren't regenerated.
test_docs_release_notes_integritystays at its base result, 0/1, because regenerating would rewrite 200+ unrelated pages.CodeQL
CodeQL Advanced completed successfully on
8d3955680: Analyze (actions), Analyze (javascript-typescript), and Analyze (python) all passed. The PR-head code-scanning query (refs/pull/1724/head) returns no alerts. The seven initial quality annotations from9c18dbfedwere addressed in the cleanup commit: redundant inheritedpublic_messageassignments were removed, fatal worker signals are re-raised after settlement, and regression tests cover inherited exception contracts and fatal exits. No alerts were dismissed.Follow-ups
None of these is a new phase. Each one fits under #1550.
Capabilities
Bounded in-run waits, so
render_file,tabular_analyzeand file deliverables can replay.web_search,deep_researchandurl_fetch, through a server-side role snapshot with a maximum age (option b).Replaying
tabular_merge.Microsoft 365 steps, through the workflow's own M365 connection with Run as = self.
Run results and recovery
A failed replay records its fixed reason, but no typed result.
The workflow conversation gets both the orchestration answer and the workflow result message. Collapse them to one.
A durable restart starts a new orchestration attempt instead of resuming the same orchestration run.
A worker that doesn't stop within the 30 s grace is fenced and settled, but not finalized any further.
Creation and the card
On a save refusal, the card shows only the fixed message. Linking the refused step would help.
A chat creation path, option (A), through
workflow_propose.Plans that reference earlier conversation messages, prompt or agent seeds, or image proposals.
Other
Group workflows.
HMAC binding of the frozen plan against direct database writes.
The docs inventory doesn't cover V2 chat controls.
The docs release-notes pages are stale for 200+ versions (pre-existing).