Skip to content

Phase 5: orchestration runs existing workflows (workflow_run, start-and-link) - #1594

Merged
Paul Lizer (paullizer) merged 19 commits into
microsoft:paullizer-react-v2-uifrom
paullizer:paullizer-phase-5-orchestration-runs-workflows
Oct 1, 2026
Merged

Paul Lizer (paullizer) merged 19 commits into
microsoft:paullizer-react-v2-uifrom
paullizer:paullizer-phase-5-orchestration-runs-workflows

Conversation

@paullizer

@paullizer Paul Lizer (paullizer) commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Phase 5 of the Chat Orchestration Workflows roadmap: orchestration runs existing workflows (workflow_run, start-and-link). The coordinator's review has passed; the user merges it.

  • Users can start a saved workflow from chat. "Run my weekly digest now" makes a plan with a workflow_run step. The step names one of the requester's own durable personal workflows by a handle from the workflow planning context, and it queues the same durable run that Run in Workflows queues. The plan starts the run and links to it. It never waits for the run or reads its results; that's Phase 6.
  • A plan that starts a workflow always waits for the user to run it, whatever approval mode they chose (roadmap decision 5). Every path that saves a plan sets the floor, and claim_plan_run refuses a saved plan that lost it (409 approval_floor_required). The V2 approval card lists the workflows the plan would start and shows Paused for a paused one.
  • Each workflow starts at most once per plan. The run's request id comes from the plan's first attempt and the step. A retry, a second tab or a crash links the run instead of starting another, and an orphaned run record heals. The answer gets a server-written note: which workflows started, which didn't and why, and where each run's progress and results appear. In V2, Started workflows links under the answer show each run's status and open it in Workflows with Open run (Track P's link). They're read from a new requester-only route.
  • Admins: the new setting Run Workflows From Chat (enable_chat_orchestration_workflow_runs) is off by default and independent of Propose Workflows From Chat. Runs also need personal workflows (allow_user_workflows), the WorkflowUser role when require_member_of_workflow_user is on, durable execution, and workflow_run in Capabilities when that list is narrowed. With the setting off, nothing changes: a golden captured on the unmodified base pins planning byte for byte.

What's in it:

  • Capability and plan checks (functions_orchestration_registry.py, functions_orchestration_workflow_runs.py, the planner and schema). workflow_run has the Gather role, at most 3 steps per plan with distinct handles, a static handle input only, and no consumers of its output. The planner gets one repair round. A step that still fails is dropped with its bindings, and server-written workflow_run_notes say why; model-supplied notes are discarded. With the setting off the capability is dormant: it's skipped before any check and records no reason.
  • Workflow planning context (functions_orchestration_workflow_context.py). It serves proposals and runs from one bounded read. When runs are gated in, it ranks workflows the user named first, then durable ones (500 scanned, top 20 offered).
  • The step (adapter_workflow_run). It re-checks the settings gate, the private conversation and the workflow, then computes the run id and reads the run. If this plan already started the run, it links it with no queue call and no admission checks. Only a new run needs a signed-in session, the role, durable execution and no Microsoft 365 wait. Unavailable outcomes complete the step with a closed reason, and only runtime unavailability fails it. The step writes a new chat_invocation on the run; MCP's mcp_invocation is untouched.
  • workflow_run_id_for_request (functions_workflow_runtime.py). The queue and the step now use one formula for a durable run's id.
  • External effects from the registry. The executor and recovery read external_effect_capability_ids() instead of a hard-coded set, and workflow_run is one of them. A failed or interrupted run step is effects_uncertain, and a retry asks for confirmation.
  • Route: GET /api/v2/orchestration/runs/<run_id>/workflow-runs (requester-only, private conversations only, sanitized 503 on storage errors).
  • V2: OrchestrationWorkflowRunNotice on the approval card, WorkflowRunLinks under the answer, a Workflow row in the run view, a recovery line, and a client floor guard in orchestrationController.ts.

Docs: the new docs/explanation/features/CHAT_ORCHESTRATION_WORKFLOW_RUNS.md, docs/admin/orchestration.md, docs/admin/workflow.md, docs/guides/trigger-a-workflow.md, docs/guides/review-and-edit-orchestration-plans.md, docs/reference/chat-controls.md, a cross-link in CHAT_ORCHESTRATION_WORKFLOW_PROPOSALS.md, docs/_data/features.yml, the v0.261.212 release notes, and the regenerated docs/_data/app_surface.yml.

Linked issue

Closes #1551
Part of #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

Testing / validation

Head is 678f56c and base is 4f77838, which is both the merge-base and the V2 tip. Some runs are at an earlier commit:

  • df8bfd0 differs from the head only by one added test in test_orchestration_workflow_run_link_routes.py, which kills the coordinator's L7 mutant (see Mutation testing).
  • 99ca676 differs from df8bfd0 only by the CodeQL fix (see CodeQL). That fix moves one pure function into the schema and points the capability test's check of it there, removes an unused constant and three tests' unused imports, adds test_orchestration_workflow_run_imports.py and updates one row each in the feature doc's Files and Test coverage tables. It changes no V2 file.
  • 191cf7f differs from 99ca676 only by the XSS fix (see CI fix). That fix changes two V2 files, one line in scripts/check_xss_sinks.py, the feature doc and the two link tests.
  • 18a2288 differs from 191cf7f only by one added test in test_orchestration_workflow_run_adapter.py.

Everything the XSS fix touches was re-run at 99ca676: the Phase 5 suites, Node, typecheck, the run-links Playwright file, the checker tests, the mutations, the guardrails and the docs tests. These ran again at df8bfd0:

  • the 12 Phase 5 suites, including the new imports test, normally and under python -O
  • 60 more test files that reach the schema: the 25 that import it, 33 that load it through another module, and test_orchestration_plan_revision_routes.py and ui_tests/test_v2_orchestration_plan_editor_backend.py, which reach it through the routes
  • Node, the typecheck, the new Playwright files and the XSS checker tests
  • the CodeQL fix's mutations, the guardrails, the docs tests and the three-dot checks

These ran again at the head: the 12 Phase 5 suites normally and under python -O, Node, the typecheck, the XSS checker tests, the L7 mutant and its control, the guardrails, the docs tests and the three-dot checks. The head adds one test function to one Python test file, and that can't change the other results: the Playwright files and the 60 schema files don't load that file, and an added test can only kill more mutants, never fewer. So those runs and the CodeQL fix's mutations weren't repeated.

Python tests ran in a dedicated venv (Python 3.12.10) that pins the app's Flask 3.1.3 and Werkzeug 3.1.6 from requirements.txt. It was created with --system-site-packages, so pytest 9.0.3 and Playwright 1.58.0 come from the machine's Python, whose own Flask and Werkzeug pair is mismatched. The environment was PYTHONPATH=application\single_app;functional_tests;ui_tests and PYTHONDONTWRITEBYTECODE=1. Each file ran in its own pytest process.

Phase 5 suites

Command: python -m pytest <file> -q -p no:cacheprovider -W ignore::DeprecationWarning --tb=line -rfE, then the same command under python -O. Results are at the head. Every file exited 0 both ways.

File Passed Passed under python -O
functional_tests/test_orchestration_workflow_run_adapter.py 129 129
functional_tests/test_orchestration_workflow_run_answer.py 18 18
functional_tests/test_orchestration_workflow_run_approval_floor.py 39 39
functional_tests/test_orchestration_workflow_run_capability.py 79 79
functional_tests/test_orchestration_workflow_run_effects.py 10 10
functional_tests/test_orchestration_workflow_run_imports.py 13 13
functional_tests/test_orchestration_workflow_run_link_routes.py 63 63
functional_tests/test_orchestration_workflow_run_planning_context.py 80 80
functional_tests/test_orchestration_workflow_runs_admin.py 14 14
functional_tests/test_orchestration_workflow_runs_off_golden.py 5 5
functional_tests/test_orchestration_dependency_allowlist.py 41 41
functional_tests/route_tests/test_route_blueprint_policy_inventory.py 10 10
Total 501 501

The tests make every side-effecting call before asserting on its result. Under python -O, pytest still rewrites the asserts in the test files themselves, while asserts elsewhere, in the application and in helper modules, are stripped. The only -O warning is pytest's notice about that.

V2

  • Node: from application/v2_ui, node --test ..\..\functional_tests\test_v2_orchestration_workflow_run_floor.mjs ..\..\functional_tests\test_v2_orchestration_workflow_run_links.mjs. Result at the head: 18 passed, 0 failed.

  • Full Node run (Node v24.11.0): node --test ..\..\functional_tests\*.mjs. The head has 101 tests and 100 pass; the base has 83 and 82 pass. The one failure is the same on both: test_v2_workspace_action_authoring_logic.mjs (AssertionError at m365_calendar_plugin.py).

  • Typecheck: npm run typecheck (tsc -b --noEmit) exited 0 at the head.

  • New Playwright tests: python -m pytest ui_tests/test_v2_orchestration_workflow_run_floor.py ui_tests/test_v2_orchestration_workflow_run_links.py ui_tests/test_admin_orchestration_workflow_runs.py -q. Result at df8bfd0: 47 passed.

    • The first run there failed on the v2_admin_settings fixture's stale-bundle check, which the admin file uses. Restoring the V2 files after the mutation runs had made them newer than static/v2, though their content was unchanged. After npm run build, all 47 passed.
    • The chat files bundle their harness script from the current V2 sources with esbuild, rebuilding it whenever a source file is newer. They take the production CSS from the ui_tests/artifacts/orchestration-plan-editor build, whose CSS and script are SHA-256 identical to the rebuilt static/v2 bundle.
  • Existing orchestration Playwright files at 18a2288, through pytest: all 17 files passed, 314 tests in total:

    • admin planner_model 9, admin actions 8, admin workflow_proposals 7
    • actions 16, conversation_context 5, approval_persistence 23, generated_images 3, model_picker 11
    • dependency_plans 32, plan_editor_backend 7, plan_editor 35
    • recovery_backend 18, recovery 32, planning_retry 15
    • streaming_bubble 3, workflow_proposal_card 22
    • outputs 68. It hung in the two-worker batch with every process at 0 CPU, and passed 68 of 68 when run alone (993 s).

    The XSS fix changes only how the run links build their to value; the rendered DOM is the same. The CodeQL fix changes no V2 file. recovery_backend and plan_editor_backend drive backend code that uses the schema, so both ran again at df8bfd0: 18 passed and 7 passed. recovery_backend also logs 18 RuntimeWarnings ("coroutine 'BaseEventLoop.shutdown_asyncgens' was never awaited") from the unchanged functional_tests/test_support/orchestration_recovery.py:216. The base gives the same 18 passed and the same 18 warnings.

  • __main__-only UI files: eight files set a module-level page in main(), so they ran as python -u ui_tests/<file>. All ran with 0 skipped:

    File Passed
    composer 6/6
    drawer_views 3/3
    elicitation 6/6
    pending_approval_resume 5/5
    plan_card 7/7
    plan_editing 5/5
    run_hydration 7/7
    auto_open 1/5

    auto_open is 1/5 at base too, with the same failures. Its fixture _plan() lacks the turn_id and conversation_id that orchestrationController.onPlan requires.

Regression sweep against the base

The sweep covered 777 functional test files at 18a2288, one pytest process per file:

  • 621 selected files:

    Group Files
    test_orchestration_* 146
    *workflow* 142
    *chat* 113
    test_v2_* 96
    *conversation* 34
    test_admin_* 33
    *m365* 28
    *mcp* 14
    route_tests 9

    The selection also included the return-False guard, the version guardrails, the settings tests, and the analyze and data-management orchestration tests.

  • 156 more files: every other functional test that references a changed module.

549 files exited 0. The other 228 were re-run at the base and compared by failing test id. No failing id is new.

  • pytest reports functional tests that return False as passed #1572 guard files. 55 of the 228 fail under pytest only through the return-False guard, which hides the reason. Each also ran as python -u <file> at head and at base:
    • 53 printed the same output after normalizing paths, times and ids.
    • test_control_center_functionality.py differs only in the version it prints.
    • test_workflow_feature.py timed out at the base under load. Alone at the base it matched the head.
  • test_tabular_row_orchestration_scale.py ran with --deselect ...::test_phase_six_rolling_pool_pauses_dispatch_at_checkpoint_high_water, because that test stalls on both sides. Head and base were both 10 failed and 66 passed, with the same 10 failing tests.
  • test_workflow_flow_inspection.py timed out in the sweep only because it runs for about 40 minutes. Alone, with a 5400-second limit, it passed 27 of 27 at 18a2288 and 27 of 27 at the base, in 2437 seconds each.

Failures that also happen at the base:

  • test_orchestration_plan_revision_routes.py::...::test_document_removal_is_not_lost_when_editor_opens
  • test_orchestration_turn_recovery_identifiers.py::test_run_record_keeps_the_turn_and_question_ids
  • Four tests in test_workflow_runtime_integration.py (NameError: WORKFLOW_SCHEDULED_TRIGGER_TYPES)
  • test_orchestration_failure_telemetry.py, 14 parameters. These fail only after test_orchestration_elicitation_context.py in the same process, because that file leaves sys.modules stubbed.
  • ui_tests/test_v2_orchestration_auto_open.py through __main__: 1 of 5, with the same failures at the base (see V2).
  • The release-notes and link docs checks below.

Load flakes. Each of these matched the base when re-run alone:

  • A subprocess timeout in test_orchestration_external_error_markers.py.
  • test_orchestration_harness_execution.py: its nested pytest collided with another worker's temp directories.
  • Four processes that hung until the 1500-second timeout, two at the head and two at the base.
  • test_content_screening_model.py: its fixed-wait timing tests failed in 1 of 3 solo rounds at the head and 1 of 3 at the base.
  • ui_tests/test_v2_orchestration_outputs.py hung in the two-worker UI batch after 49 passing tests, with Python, Node and Chrome all at 0 CPU. Alone it passed 68 of 68.

At df8bfd0. The CodeQL fix changes the schema, so every test file that reaches it ran again, one pytest process per file, and was compared with 18a2288 by failing test id. No failing id is new.

  • 25 files that import the schema: all exited 0, with 1,641 passed, 2 xfailed and 88 subtests. The 16 warnings are PytestReturnNotNoneWarnings from three files this PR doesn't change: elicitation_schema 5, prompt_instruction 5 and run_hydration_routes 6. test_orchestration_harness_execution.py passed 649 with 2 xfailed, where the 18a2288 sweep hit the temp-dir load flake above.
  • 33 files that load it through another module: all exited 0, with 914 passed and 88 subtests. The counts match 18a2288 for the 32 functional files. The 33rd is ui_tests/test_v2_orchestration_recovery_backend.py, covered under V2 above. The 24 warnings are those 18 RuntimeWarnings and 6 PytestReturnNotNoneWarnings from test_orchestration_agent_selection.py, which the base also shows.
  • Through the routes: test_orchestration_plan_revision_routes.py passed 28 with 14 subtests, plus the base failure listed above. ui_tests/test_v2_orchestration_plan_editor_backend.py passed 7.

Mutation testing

147 mutants were applied one at a time and run against the Phase 5 suites. All 147 were killed. They include the ten that coordinator note 10 asked for:

  • the Microsoft 365, durable, session and role checks moved before the run lookup
  • the step's run id formula drifting from the queue's
  • the proposal quota blocking runs
  • model-supplied workflow_run_notes kept
  • the sidecar leaking through public_step_record or SSE
  • named-first ranking removed
  • retry_on_transient set on workflow_run, and workflow_runtime_unavailable added to TRANSIENT_FAILURE_CODES
  • a new run queued without a session
  • a non-404 read error treated as missing

Every mutant that survived a first run got a new test. Two context roots that never run a step today are pinned by an AST check (decision 17).

The XSS fix added 7 of the 147, and the new guardrail test or the links .mjs kills each one:

  • workflowRunHref removed from the approved builders
  • the link built from a property
  • the link built from a local variable
  • href put back on the item
  • the builder no longer returning the fixed path
  • the parser adding a stray key
  • the parser swapping the two ids

The CodeQL fix added 8 more, run at df8bfd0. Three re-target earlier mutants whose code moved or changed shape: the catalog lookup ignoring the handle map, which now lives in the schema, and the two run-link mutants that the XSS fix reshaped (the client's unavailable reason made optional, and the component hiding it). Five are new, and the capability test or the new imports test kills each one:

  • the catalog lookup ignoring the catalog
  • the schema importing the lookup back from the run module
  • the run module defining its own lookup again
  • an unresolvable lazy import in the run module
  • an unresolvable lazy import in the links module

The coordinator's review added the last one, L7, run at the head. It deletes the link route's own conversation check, record.get('conversation_id') != conversation_id in route_backend_orchestration.py, so the route would list the links of a run record that names another conversation. The new test_a_run_whose_record_names_another_conversation_is_not_found in test_orchestration_workflow_run_link_routes.py kills it: the route must answer 404 run_not_found without reading the saved workflow or its workflow run, and the record still opens from the conversation it names. With that test deselected, the mutant survives (62 passed), so the new test is what kills it.

The coordinator's own mutation run: 38 of 38 killed after the L7 test (37 of 38 at df8bfd0).

CI fix

The first CI run, on 191cf7f, failed xss-sink-check: [ts-jsx-url-attribute] at WorkflowRunLinks.tsx:47, <Link to={item.href}>. The checker approves a JSX URL only when it's a call to a reviewed same-origin builder; it can't trace a property. My local guardrail run had missed it because the guardrail script then fed the checker only .js, .html and .py, 21 files, while CI also checks .ts, .tsx, .jsx and .mjs, 32 files.

The fix, in 99ca676, follows Phase 4's workflowProposalLink (e72baec):

  • The item keeps the run's {workflowId, runId}.
  • The component calls workflowRunHref(...) in the Link.
  • workflowRunHref joins TS_SAME_ORIGIN_URL_BUILDERS. It returns the fixed same-origin path /workspace/workflows?${URLSearchParams}, and a new test pins that.

The CI-style checker, run on the head over the 32 changed application/ files, prints "XSS sink validation passed for 32 file(s)". At the head, test_xss_guardrails_checker.py passes 12 tests, and the new functional_tests/test_v2_orchestration_workflow_run_links_xss_guardrail.py passes 4 of 4 under pytest, under python -O and through __main__. It checks four things:

  • The links files pass the checker.
  • The Link calls the approved builder, and the item has no href.
  • A synthetic <Link to={item.href}> is flagged, while the builder call passes.
  • The builder's fixed path.

Guardrails

Command: run_v2_guardrails.py <worktree> 4f778386b 678f56c8e phase5, over the 62 changed files. All six checks passed at the head:

  • broken-access-control: 20 files.
  • xss-sinks: 32 files, including the 11 V2 .ts and .tsx files. This is the same file set CI uses; see CI fix.
  • swagger-routes: 2 route files.
  • python-syntax: 488 top-level single_app .py files compile.
  • malicious-pr-review: no blocker findings and no dependency findings. Its "needs investigation" items are keyword markers. The only external hosts are test fixtures: evil.example in hostile-name tests, the json-schema.org $schema in the golden, and simplechat.example as a URL-parse base. None of the later fixes or tests adds a URL.
  • v2-ts-sinks: 11 ts/tsx files, no hits in added lines.

Docs

Each test ran as python functional_tests\<test>.py, at the head and at the base for parity.

Test Head Base
test_docs_app_surface_coverage.py 7/7 7/7
test_docs_site_quality.py 6/6 6/6
test_docs_latest_release_catalog_parity.py 6/6 6/6
test_docs_web_search_accuracy.py 3/3 3/3
test_docs_link_integrity.py 2/5 2/5, the same failures
test_docs_json_gem_security_fix.py fails fails (the json version in Gemfile.lock)
test_docs_release_notes_integrity.py 0/1 0/1
  • Link integrity: both sides report the same broken links: 11 relative links (of 1,119 at the head and 1,107 at the base), 9 site page links (of 788 and 785) and 2 features.yml links (of 68). The head's 12 extra relative links and 3 extra page links all resolve.
  • Release-notes integrity: the generated docs/explanation/release-notes/ pages are already stale on the V2 tip, missing 147 versions (decision 22).

Three-dot against origin/paullizer-react-v2-ui (4f77838)

  • git diff origin/paullizer-react-v2-ui...HEAD -- application/single_app/config.py: only VERSION = "0.261.210" → "0.261.212".
  • git diff --numstat origin/paullizer-react-v2-ui...HEAD -- docs/explanation/release_notes.md: 30 0, so lines are only added.
  • git grep -n "0.261.211" over the 62 changed files: no matches.
  • python .\scripts\build_docs_inventory.py leaves docs/_data/app_surface.yml unchanged.

Documentation

  • Release notes updated, or not needed: a new ### **(v0.261.212)** section on top, and everything below it is byte-identical (git diff shows 30 added lines and none removed).
  • Feature documentation updated, or not needed: CHAT_ORCHESTRATION_WORKFLOW_RUNS.md (new), plus the admin, guide and reference pages listed under Summary.
  • Fix documentation updated, or not needed: not needed; this PR adds a feature.

Security checklist

  • New Flask routes include @swagger_route(security=get_auth_security()): the one new route, GET /api/v2/orchestration/runs/<run_id>/workflow-runs, has it before @login_required and @user_required, and it's in the route policy inventory.
  • Settings sent to non-admin frontends use sanitize_settings_for_user(): no settings are sent to non-admin frontends. The new setting is read on the server and appears only in the admin pages. The link route returns per-run link records only, never settings.
  • Browser JavaScript is served from local SimpleChat static assets only; no CDN-hosted JS: no new dependencies or assets.
  • No secrets, keys, connection strings, or local-only artifacts are included: no build output (static/v2, ui_tests/artifacts) is committed.

Decisions made autonomously

From the approved plan

  1. Two independent settings. Runs aren't covered by Propose Workflows From Chat: a proposal creates standing work, while a run is one approved run of a workflow the user already has.
  2. The workflow_run capability. It has the Gather role and max_per_plan=3, with distinct handles, a static handle input only, and no consumers of the run output (workflow_run_consumed). Phase 6c may lift the no-consumer rule.
  3. The approval floor is a descriptor key. approval_floor: 'manual' is recorded on the plan as approval.floor. claim_plan_run backs it up with a 409 approval_floor_required, and V2 has a client guard.
  4. Paused workflows can run, as with the manual Run button. The card says Paused, and the workflow stays paused after its run.
  5. The request id comes from the attempt root plus the step, in its own UUID namespace. The step looks up the existing run before queueing, and an orphaned unbound run heals because the same request is queued again.
  6. Chat invocation metadata is a new chat_invocation field on the run. MCP's mcp_invocation is untouched.
  7. Unavailable outcomes complete the step with a closed reason, so the rest of the plan runs. Only runtime unavailability fails it (workflow_runtime_unavailable, retryable, effects uncertain).
  8. The answer gets a deterministic server note, with no new deliverable kind and no post-back promise. The note is kept on cancelled runs, because a run may already have started.
  9. The links use a requester-only route and card, not answer metadata. Blocked entries hide workflow names.
  10. Catalog ranking: workflows the user named come first, then durable ones, when runs are gated in. The 500-scan and top-20 limits are documented.
  11. The confirm-retry wording is generic and names saved workflows.

Made while implementing

  1. No signed-in session. A background continuation (the orchestration scheduler) has no request, so RunContext.signed_in_session records whether the identity came from a session, matching build_external_identity_reader (Fix orchestrated web search and deep research in V2 chat #1582). Without a session the step still links a run that already exists. It never queues a new one; it fails with external_session_required, whose message now mentions saved workflows.
  2. No automatic transient retry. workflow_run doesn't set retry_on_transient, and workflow_runtime_unavailable isn't in TRANSIENT_FAILURE_CODES. A behavioral test also asks the executor's own retry check (_should_retry_transient) about a failed workflow_run step, with the transient codes (provider_timeout, connection_failed, provider_failed) and with workflow_runtime_unavailable. A document_search step with provider_timeout is the positive control. Each half has its own mutant.
  3. Direct reads. The step and the route read the conversation, workflow and run containers directly: a 404 is missing, and any other storage error is workflow_runtime_unavailable (503 from the route). The get_personal_workflow* helpers turn every error into None.
  4. Unknown runtime conflict codes, including the store's not_found, read as workflow_run_not_started. The runtime's code and message never reach the user or the retained value.
  5. retry_principal_fix: signed_in_session checks getattr(principal, "bridge", None) is not None at all three RunContext construction sites, because retry validation and some fixtures pass principal=None.
  6. Equivalent mutants pinned. Two context roots that never run a step today (delivery and retry validation) are pinned by an AST check that every RunContext reads its attempt root from the run record and its session from the principal bridge, so 6a and 6b inherit them.
  7. A stop between the queue call and saving the step leaves the step cancelled with effects_uncertain and no sidecar. The reply gives the generic cancel text ("Already submitted external actions may still finish."), and a retry links the same run. The note and links cover completed steps only.
  8. workflowRunHref, not v2WorkflowRunPath. The links use Track P's lib/workflowRunLink.ts. v2WorkflowRunPath in notificationLinks.ts deliberately returns null until 6b adds a run page (gotcha 61). When 6b adds one, point workflowRunHref at it too, or route both through one helper.
  9. Log fields use allowlisted keys (run_id_hash, step_id_hash, status, reason, failure_code, error_type, stage), so their values survive functions_appinsights._build_logger_extra. Tests run each log through the real allowlist.
  10. "Create & run now" is deferred (see Follow-ups).
  11. The generated release-notes pages aren't regenerated. They're already stale on the V2 tip (see Testing), and regenerating rewrites large shared generated files.
  12. Guide versions aren't bumped, following the docs-only rule.
  13. workflowRunHref is an approved same-origin URL builder in scripts/check_xss_sinks.py, as Phase 4 did for workflowProposalLink (e72baec). It returns the fixed path /workspace/workflows?${URLSearchParams}. The run links call it in <Link to>, and 6b's WorkflowRunCard can too. The alternative was a sanitize-named wrapper. That would pass the checker by its name pattern alone, which is a weaker guarantee than a reviewed allowlist entry.
  14. The catalog lookup lives in the schema. workflow_run_catalog_entry(workflow_planning, handle) is a pure function. The schema needs it to list a plan's workflows for the approval card, and the run module uses it for every run check. At 99ca676 the schema imported it lazily from the run module, and CodeQL reported six cyclic-import notes: one on that import and five on imports in the run module. It now lives in functions_orchestration_schema.py, and the run module imports it at load time, as it already did the rest of the schema. All six notes are gone at the head (see CodeQL). The schema still reaches the run module lazily, but only to check a run step (prepare_workflow_run_arguments). That's the same shape as Phase 4's proposal check. test_orchestration_workflow_run_imports.py pins this with an AST guard, and checks that the run and links modules load in fresh normal and -O interpreters, in the application's import orders, with no network call.

Coordinator review notes, as folded in

  1. Version: moved to 0.261.212 in a separate renumber commit (5bdff7a) after Show what was extracted from V2 image uploads, index text-free images, and default image generation to 2025-04-01-preview #1585 took .210. git grep 0.261.211 over the changed files is empty.
  2. Base: fast-forwarded to b0ed6ea, and the setting-off golden was captured there before any planner change (af01940). Then V2 at 4f77838 was merged with -c rerere.enabled=false (2430df0).
  3. Adapter order: (a) the settings gate, the privacy and ownership re-read, and the workflow exists and isn't deleting; (b) the run id and the run read, linking already_started with no queue call and no admission checks; (c) only then the session, the role, durable, Microsoft 365, the cancel check and the queue. There are tests for a bound run with the workflow waiting on Microsoft 365, an existing run with durable now off, and an orphan with Microsoft 365 active.
  4. Background continuations: see decision 12. There are tests for no request context with the role requirement on and off, no session with an existing run, and a session.
  5. Readers: see decision 14. A non-404 error is tested on each read and in the link route (sanitized 503, never "deleted").
  6. No automatic transient retry: see decision 13. Both halves are tested.
  7. One run id formula: workflow_run_id_for_request(workflow, request_id) is used by the queue and the step. A parity test runs the real queue_durable_workflow_run with fake services and gets the step's precomputed id.
  8. Unknown conflict codes: see decision 15. Tested.
  9. Plan-editor paths: the model revision (plan_request(..., edit_context=...)) and manual edits (validate_edited_plan → normalize_plan) are in the floor tests. A revision that adds a workflow_run step comes back floored and checked against the stored handles, and the edit prompt carries WORKFLOW_RUN_INSTRUCTIONS when the capability is available.
  10. Mutations: all ten listed mutations fail at least one test (see Testing).
  11. PR format: this body.
  12. Alert text: a follow-up only (see Follow-ups).
  13. PR review, L7: a tests-only commit (678f56c), with no VERSION change, pins the link route's own conversation check (see Mutation testing).

Follow-ups

  • Create & run now on the Phase 4 proposal card.
  • 6a: the results reader and Follow up.
  • 6b: post-back delivery (chat_delivery on the run), a live WorkflowRunCard with Cancel and Retry, the chat-list indicator, and pointing workflowRunHref at 6b's run page.
  • 6c: an in-plan wait, which may lift the no-consumer rule.
  • Phase 8 (Workflow orchestration follow-ons: group workflows, SimpleChat action parity and plan replay #1550): group workflows from chat.
  • Non-durable runs from chat.
  • A catalog scan ordered by updated_at or searched by name, to go beyond 500 workflows.
  • Orphaned-run cleanup inside the queue: tombstoning an unbound run on a later bind.
  • Alert text shows the raw trigger, for example "completed from the chat_orchestration trigger" (functions_workflow_runner.py L5455, L5484 and L5552). The V2 run history doesn't show which trigger started a run either.
  • Phase 4 log fields that App Insights drops: functions_orchestration_workflow_proposals.py logs code=, step_id= and proposal_id=, and the planner's proposal-drop log carries unavailable_reason. None are allowlisted, so only their lengths are recorded.
  • Microsoft 365 pending actions in V2: a run waiting on Microsoft 365 is finished in Workflows, not in the chat.

Shared files (for 6a)

Every existing file this PR changes, apart from config.py and the docs:

  • Orchestration: functions_orchestration_registry.py, functions_orchestration_workflow_context.py, functions_orchestration_planner.py, functions_orchestration_schema.py, functions_orchestration_plan_revisions.py, functions_orchestration_plan_editing.py, functions_orchestration_executor.py, functions_orchestration_execution.py, functions_orchestration_recovery.py, functions_orchestration_services.py, functions_orchestration_deliverables.py and functions_orchestration_adapters.py.
  • Workflow runtime and routes: functions_workflow_runtime.py and route_backend_orchestration.py.
  • V2: MessageList.tsx, OrchestrationPlanCard.tsx, OrchestrationRunView.tsx, OrchestrationRecoveryNotice.tsx, lib/orchestration.ts, lib/orchestrationController.ts, lib/orchestrationPlan.ts and lib/workflowRunLink.ts.
  • Settings and admin: functions_settings.py, admin_settings_fields.py, route_frontend_admin_settings.py and templates/admin/_panes/chat-orchestration.html.
  • Tests: functional_tests/route_tests/test_route_blueprint_policy_inventory.py and functional_tests/test_orchestration_dependency_allowlist.py.
  • Tooling: scripts/check_xss_sinks.py, one line: workflowRunHref in TS_SAME_ORIGIN_URL_BUILDERS (decision 24).

functions_orchestration_schema.py now also defines workflow_run_catalog_entry, the lookup from a run step's handle to its catalog entry (decision 25). Code that needs it should import it from the schema; it isn't in the run module's __all__.

New logic is in new modules: functions_orchestration_workflow_runs.py, functions_orchestration_workflow_run_links.py, OrchestrationWorkflowRunNotice.tsx, WorkflowRunLinks.tsx and lib/orchestrationWorkflowRuns.ts.

CodeQL

The code-scanning API returns 403 for this token, so this section comes from the CodeQL check runs and their annotations.

Commit Check run Conclusion Title
99ca676 110260810254 success "13 new alerts including 3 medium severity security vulnerabilities"
df8bfd0 110278225449 success "3 new alerts including 3 medium severity security vulnerabilities"
678f56c (head) 110314448032 success "3 new alerts including 3 medium severity security vulnerabilities"

All 11 check runs at the head completed with success.

The ten notes fixed in df8bfd0

None of them was a security alert, and the fix changes no behavior. Line numbers are at 99ca676.

  • Cyclic import (6). Schema L1007, its lazy from functions_orchestration_workflow_runs import workflow_run_catalog_entry, and five imports in the run module: L45 (functions_orchestration_registry), L52 (functions_orchestration_result_runtime), L54 (functions_orchestration_schema), L63 (functions_orchestration_workflow_context) and L474 (its lazy import of workflow_run_id_for_request from functions_workflow_runtime). workflow_run_catalog_entry now lives in the schema (decision 25), so the schema's L1007 import is gone. None of the six is reported at the head.
  • Unused global variable (1). RULE_CONSUMED in the run module (L87), removed.
  • Unused import (3 alerts, 4 names). All removed:
    • importlib in test_orchestration_workflow_run_answer.py (L18)
    • NOT_DURABLE_ID and _workflows in test_orchestration_workflow_run_capability.py (L36, one alert)
    • WORKFLOW_RUN in test_orchestration_workflow_run_effects.py (L34)

What ran again after the fix, at df8bfd0 and at the head, is under Testing. Mutation testing lists the 8 mutants the fix added.

Log Injection (3, medium): log_event's existing sinks

All three runs report Log Injection at the same three lines of functions_appinsights.py, with the same messages:

  • logger.exception (L515)
  • logger.log (L531)
  • the fallback logger's fallback_logger.log (L553–560)

This PR doesn't change functions_appinsights.py: git diff --stat 4f778386b...HEAD -- application/single_app/functions_appinsights.py is empty.

Each message repeats This log entry depends on a [user-provided value](n). for 74 numbered sources and is cut off at 4,096 characters partway through a 75th. It doesn't say where the sources are, so it can't show whether any of them are new. The head's run summary adds: "Alerts not introduced by this pull request might have been detected because the code changes were too large."

What this PR passes to log_event. The one new route reads one request value: conversation_id from the query string (route_backend_orchestration.py L2907). Its only log call (L2924) records conversation_id and the run_id path value as SHA-256 hashes through workflow_log_context. Every log call this PR adds has a constant message. Each records only hashed ids, application codes, numbers and exception class names:

File Lines Fields, across those lines
route_backend_orchestration.py 2924 stage, conversation_id_hash, run_id_hash, error_type
functions_orchestration_workflow_runs.py (_log, L224) 492, 766, 782, 790, 822 stage, run_id_hash, step_id_hash, status and reason (both from closed sets), failure_code, error_type
functions_orchestration_workflow_run_links.py (_log, L89) 275 stage, reason (workflow_run_link_mismatch)
functions_orchestration_workflow_context.py (_log_context, L790) 860, 867, 1037 stage, reason (workflow_context_unavailable), error_type, workflow_count, duration_ms
functions_orchestration_registry.py 337 reason (workflow_context_unavailable), error_type
functions_orchestration_executor.py 660 run_id_hash, conversation_id_hash, step_id_hash, capability_id, error_type
functions_orchestration_planner.py 1375, 1384 stage, conversation_id_hash, turn_id_hash, reason, attempt, revision, validation_code, validation_rule; 1375 also has unavailable_reason, and 1384 has note_count and remaining_count

Planner L1375 is Phase 4's proposal-drop log. This PR re-indents it and reads the rule from proposal_error instead of exc; its keys are unchanged.

What log_event does with them (unchanged code):

  • sanitize_log_message replaces runs of \r, \n and \t with a space (LOG_CONTROL_CHAR_RE, L73) and redacts secret-like values.

  • The record message is a constant: [SIMPLE_CHAT_LOG_EVENT], or [SIMPLE_CHAT_LOG_FALLBACK] for the fallback logger.

  • _build_logger_extra (L249) keeps a string's text only under allowlisted keys:

    • hash keys keep only 64-character lowercase hex
    • code keys keep only snake_case codes of up to 80 characters
    • a short list of diagnostic keys, including reason, status, stage and error_type, keeps sanitized text

    Every other string is recorded as its length.

Triage. I made no change for these. The coordinator's review confirmed that the three annotations are these pre-existing sinks, alerts #2551–#2554, which the user is handling. #1580's body records that Phase 3b (#1577) matched the Log Injection alerts at these three lines and L419 to those alerts, which are open on V2 and Development. This token can't read alert state (the code-scanning API returns 403), so I haven't checked them myself. The CodeQL check run succeeded at all three commits with these alerts reported.

Record, on the unmodified 0.261.209 base, the workflow planning context,
the planner model messages, the repair/degrade result and the capability
resolution for proposals-only orchestration. Phase 5 must keep all of
them byte-identical while enable_chat_orchestration_workflow_runs is off.

The request names the oldest workflow and the newest workflow is not
durable, so any catalog ranking for workflow runs that leaked into
proposals-only planning would change the fixture.

Bump VERSION to 0.261.211.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
enable_chat_orchestration_workflow_runs is off by default and only a
real boolean True turns it on: update_settings coerces it, the Classic
form reads it like its neighbouring checkboxes, and the V2 schema shows
it under Capabilities only while Chat Orchestration and personal
workflows are on. It is independent of Propose Workflows From Chat.

The workflow context gains workflow_runs_configured,
workflow_planning_configured (either setting), a settings-only
workflow_run_settings_gate that checks the allowlist for workflow_run
itself, and workflow_run_gate, which adds the WorkflowUser rule. Nothing
uses them yet, so orchestration is unchanged.

Documents the setting on the admin page, claims the key and regenerates
the docs inventory.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The planning context now exists when proposals or runs are gated in, so
workflow_run (next commit) can read the requester's catalog without the
proposal setting.

- build_workflow_planning_context checks privacy first, then builds the
  proposal context verbatim when proposals are gated in. When runs are
  gated in, it adds a workflows-only catalog if the proposal context is
  not ready (runs-only, the per-user cap, or a failed proposal read).
  The per-user cap limits proposals only.
- With runs gated in, the catalog ranks workflows the request names
  first, then durable ones, each newest first. Proposals-only keeps
  Phase 4's newest-first order; the golden fixture proves it.
- workflow_run_ready, workflow_run_unavailable_reason and
  workflow_run_projection mirror the proposal helpers.
- /plan, /run, execution and plan editing read the context when either
  setting is on (workflow_planning_configured). The time zone and source
  scopes stay proposal-only.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A plan can name one of the requester's saved workflows to start with a
workflow_run step (Gather, low cost, at most three per plan). The step's
only argument is a catalog handle from the turn's workflow planning
context; it takes no depends_on and no inputs, so no step result can
choose which workflow runs, and no step or answer may read it.

- Registry: the descriptor, dormant until Run Workflows From Chat is a
  real True, its request gate and closed unavailable reasons.
- Schema: prepare_workflow_run_arguments checks static input, an offered
  durable handle, duplicates and the limit; consumers are rejected.
- Planner: run instructions only while the capability is offered, one
  fixed-text repair round, then each failing run step is dropped with a
  closed reason so the rest of the plan runs. Model-written
  workflow_run_notes are discarded.
- Deliverables: the planner is told about starting workflows only while
  the setting is on.
- Admin: the Run workflows capability option on both surfaces.
- The adapter is registered but fails closed, starting nothing, until
  the approval floor and run-start commits land.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A workflow_run step carries approval_floor: 'manual' on its descriptor. After
validation, normalize_plan forces approval.mode to manual and records
approval.floor = {mode: 'manual', reason: 'workflow_run'} whenever an enabled
step has a floor, whatever approval mode the admin or user asked for. Every
path that saves a plan goes through normalize_plan, so the first plan,
regenerate, replan, question answers, plan-editor revisions, plan editing and
recovery retries all keep the floor.

claim_plan_run refuses a floor plan whose saved record or plan approval is not
manual (409 approval_floor_required), so a tampered record can't auto-run.

build_plan_inputs adds inputs.workflows (handle, name, trigger summary, paused)
so the approval card names each workflow. IDs stay out of the plan.

The V2 plan card shows a floor notice, never starts a timed countdown for a
floor plan, and the controller never auto-runs one.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The workflow_run step resolves its catalog handle to one of the
requester's durable personal workflows and queues exactly one run
through queue_durable_workflow_run, with trigger_source
chat_orchestration.

- Re-checks the setting, the WorkflowUser rule and conversation privacy
  before queueing, reloads the workflow, requires durable execution and
  refuses Microsoft 365 waiting states, like the manual Run route.
- The request id is derived from the plan's first attempt and the step
  (attempt_root_run_id, now on RunContext at all three construction
  sites), so a recovery retry links to the run its first attempt started.
- Looks up the deterministic run id before queueing, so a later active
  run never hides ours, and checks cancellation right before queueing.
- Every queue exception maps to a closed reason, a tombstoned request
  included; storage outages are retryable with effects uncertain.
- Records chat_invocation on the run beside mcp_invocation, which is
  unchanged for MCP.
- Keeps workflow and run ids in a server-only step sidecar that
  public_step_record drops; recovered and reused steps rebuild it.
- A started run's result is never offered to later plans as an alias.

Refs microsoft#1551

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The coordinator set the landing order to Phase 3c (0.261.211), then
Phase 5 (0.261.212), then Phase 6a (0.261.213). This renumbers only
Phase 5's own references: config.py, its module and test headers,
its version checks and its admin settings row.

Refs microsoft#1551

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in microsoft#1585 (React V2 image indexing and upload fixes). The only
conflict was VERSION in config.py, kept at Phase 5's 0.261.212.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Recovery and the executor decided which failed steps may have acted outside
the chat from a hard-coded set of agent_invoke and action_invoke. Each
descriptor now declares external_effects, and both read the set from the
registry, so workflow_run joins them: a failed or interrupted run step marks
its effects uncertain and a retry asks the user to confirm first. The flag
stays server-side; the planner and client projections are allowlists.

The V2 retry dialog now says a saved workflow that already started is linked
again, never started twice.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A step whose checkpoint was lost after the queue is recovered from its
retained result alone, so the retry must rebuild the run link from the plan's
first attempt and the step. A run prepared outside a request has no signed-in
session and never starts a workflow, while one with a request bridge does.
Every RunContext reads its attempt root from the run record and its session
from the principal's bridge.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A plan that starts a saved workflow now ends its reply with a short, server-written
note: which workflows started, which were already started for this request, and
why any could not start, in fixed application text. Names are the workflow's own,
quoted as inline code so they cannot format the reply. The note names no IDs or
handles, says where the results appear (the run page, the workflow's own
conversation and its alerts), and never promises to post results back here.

The note is added in _finalize for every outcome, so a stopped plan still says the
workflow it started keeps running. Only completed workflow_run steps are listed; a
stop between queueing and saving the step is left to the cancel text and recovery.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A requester-only read route, GET /api/v2/orchestration/runs/<run_id>/workflow-runs,
lists the saved workflows a plan started, with each run's current state and its
workflow and run ids. Sidecars are read from the run, the step record or a retry's
producing attempt, and each is checked against the plan, the step, the requester and
the run id derived from the attempt root; a mismatch is left out and logged.

A blocked gate, a shared conversation or an answer in content review shows every
link as unavailable, with no names or ids. A deleted workflow or a run missing from
history reads as unavailable with its own reason. Non-404 read errors return a
sanitized 503.

The V2 chat mounts the links under an answer whose plan completed a workflow_run
step, only in the requester's own active personal conversation and only when nothing
is masked. Each link opens the workflow's run history with that run expanded
(workflowRunHref). The status is read once, with no polling.

Tests: 62 route, 7 node and 29 Playwright tests. All 35 c9 mutations are killed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The log pipeline keeps text only under allowlisted keys and records every
other string as a length. The workflow run step logs used reason_code,
workflow_run_status and raw run and step ids, so the codes never reached
Application Insights. Log hashed run and step ids through
workflow_log_context, and use status, reason and failure_code. The link
mismatch log uses reason instead of code.

Tests run each log through the real allowlist and check that the codes
and hashes are kept and that no raw id is logged.

Refs microsoft#1551

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds the Chat orchestration workflow runs feature doc and the 0.261.212
release notes, and updates the orchestration and workflow admin pages,
the plan review guide, the trigger-a-workflow guide and the chat
controls reference for Run Workflows From Chat, the manual approval
floor, the started workflows links and the retry behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The storage-failure test now runs the route's error log through the
real Application Insights allowlist and checks it keeps the stage, the
error type and the hashed run and conversation ids, and never the raw
ids.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Ask the executor's own transient-retry check about a failed workflow_run step with each transient code and with workflow_runtime_unavailable, using a document_search step with provider_timeout as the positive control. Results are captured before the assertions so the calls still run under python -O.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The XSS sink check flagged the Started workflows link: <Link to={item.href}>
took its URL from a property the checker can't trace. The parsed link item
now keeps the workflow and run ids the response check accepted, and the
component calls workflowRunHref in the link itself. workflowRunHref only
returns the fixed /workspace/workflows path with the ids encoded as query
values, so it joins the checker's reviewed same-origin URL builders, as
Phase 4's workflowProposalLink did.

A new guardrail test runs the checker over the run link files, pins the
builder call and the approval, and checks that a link from a plain property
is still flagged.

Refs microsoft#1551

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread application/single_app/functions_orchestration_schema.py Fixed
Comment thread application/single_app/functions_orchestration_workflow_runs.py Fixed
Comment thread application/single_app/functions_orchestration_workflow_runs.py Fixed
Comment thread application/single_app/functions_orchestration_workflow_runs.py Fixed
Comment thread application/single_app/functions_orchestration_workflow_runs.py Fixed
Comment thread application/single_app/functions_orchestration_workflow_runs.py Fixed
Comment thread application/single_app/functions_orchestration_workflow_runs.py Fixed
Comment thread functional_tests/test_orchestration_workflow_run_answer.py Fixed
Comment thread functional_tests/test_orchestration_workflow_run_capability.py Fixed
Comment thread functional_tests/test_orchestration_workflow_run_effects.py Fixed
Paul Lizer (paullizer) and others added 2 commits October 1, 2026 04:06
CodeQL reported cyclic imports between the plan schema and the workflow
run module, an unused global, and unused imports in three tests.

- Move workflow_run_catalog_entry into functions_orchestration_schema.
  The run module already imports the schema at load time, so the
  approval card and the run checks share one lookup and the schema no
  longer imports the run module back for it. The schema still reaches
  the run module lazily, only to check a run step.
- Remove the unused RULE_CONSUMED constant. The schema reports consumed
  run outputs under its own workflow_run_consumed rule.
- Remove unused imports from the capability, answer and effects tests.
- Add test_orchestration_workflow_run_imports.py: the run and link
  modules load in fresh normal and optimized interpreters in the
  application's import orders with no network, every lazy import into
  and out of them resolves, and an AST guard pins the schema's single
  lazy import of the run module.
- Update the feature doc's file and test tables.

Refs microsoft#1551

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

Adds a link-route test that plants a copy of a started run under another
conversation's partition and asserts the route returns 404 run_not_found
without reading the workflow or its run, while the same record still opens
from the conversation it names. This kills the coordinator's L7 mutation,
which deletes the route's own conversation_id compare.

Tests only; no VERSION change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) marked this pull request as ready for review October 1, 2026 10:11
@paullizer
Paul Lizer (paullizer) merged commit 5fc50a5 into microsoft:paullizer-react-v2-ui Oct 1, 2026
11 checks passed
Paul Lizer (paullizer) added a commit to paullizer/simplechat that referenced this pull request Oct 1, 2026
microsoft#1594 (Phase 5) merged into paullizer-react-v2-ui first and took
0.261.212, so 3c moves to 0.261.213. This is a mechanical renumber of
this PR's own files: config.py, the release-notes heading, the docs'
version lines and mentions, and the functional and UI test headers and
version assertions. Every changed line differs only by the version.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer) added a commit to paullizer/simplechat that referenced this pull request Oct 1, 2026
Brings in Phase 5 (microsoft#1594, 0.261.212). Conflicts resolved:
- application/single_app/config.py: keep VERSION 0.261.213 (3c).
- docs/explanation/release_notes.md: the v0.261.213 section goes on top,
  then Phase 5's v0.261.212 section byte-identical, then v0.261.210 and
  earlier unchanged.

docs/admin/workflow.md and docs/_data/features.yml merged cleanly; both
sides are kept.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer) added a commit to paullizer/simplechat that referenced this pull request Oct 1, 2026
Status update 4 for the chat orchestration workflows roadmap (docs only).

- Status table: 3c done (microsoft#1593, v0.261.213), so Phase 3 is complete;
  Phase 5 done (microsoft#1594, v0.261.212); 6a in review (microsoft#1592).
- 3c shipped block and its follow-ups; Phase 5 shipped block and its
  follow-ups; 6a in-review note; 6b run deep-link guidance now covers
  Phase 5's workflowRunHref.
- Base failures on paullizer-react-v2-ui that the phase PRs report,
  with verified causes.
- Gotchas 62 (result answers outside the message list) and 63 (Retry
  and Edit of a result-backed turn).

Refs microsoft#1543

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer) added a commit to paullizer/simplechat that referenced this pull request Oct 1, 2026
Brings in the workflow_run capability (0.261.212). Conflicts:
- config.py keeps VERSION 0.261.213; a separate commit renumbers.
- release_notes.md keeps the 0.261.213 section above Phase 5's
  0.261.212 section, which is unchanged.
- chat-controls.md keeps the higher front-matter version and puts
  Phase 5's "Workflow runs (V2 interface)" section before
  "Workflow results in chat (V2 interface)".
The settings, admin fields, admin route, features.yml, docs and the
blueprint inventory test merged cleanly with both sides kept.
app_surface.yml is regenerated, not merged: both sides counted 123
capabilities, and the merged surface has 124.

Part of microsoft#1546

Co-authored-by: Copilot App <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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants