Repository navigation
Phase 7a: hand off large chat requests to a one-time workflow (server, 0.261.238) - #1640
Merged
Paul Lizer (paullizer) merged 33 commits intoOct 6, 2026
Conversation
Pins planning, capability resolution, the browser capability list, and the Phase 4 and Phase 5 route responses for every hand-off gate variant, captured on the unmodified base before any hand-off change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… gates - New default-off enable_chat_orchestration_workflow_handoff key and a per-user daily hand-off limit (default 5, 1-100) on the orchestration admin tab. - Server-only origin.one_time marker and a one_time_status runtime field. - workflow_handoff capability: Reason role, manual approval floor, max_per_plan 1, dormant until the key is on and silent when unusable, so every existing projection and prompt stays byte-identical. - Hand-off gates in the workflow planning context module. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Phase 7a (#1549). A pure builder turns a hand-off blueprint (a loop over named documents or a bounded workspace query, a per-item review task and a report task) into a definition_version 3, durable_execution workflow with a current-item Analyze, a collect node and a report task. The draft service gains a closed hand-off schema, handle normalization, dry run, create and edited-payload create paths. Hand-offs are created paused and one-time, with notify-only alerts and no M365 or Run as. Phase 4's blueprint, payloads and dry runs are unchanged (pinned by digests). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds the workflow_handoff adapter and planner helpers, the hand-off plan rules (static input, no consumers, exclusive with workflow proposals and runs, and the plan budget code offered only with hand-off), the hand-off planning marker in the workflow planning context, and the scope seeds the plan route passes to it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…lizer-phase-7a-workflow-handoff
The fixture was written again with --write-golden from the unmodified V2 commit f7e268c (0.261.233). Apart from the captured_from label it is byte-identical to the ca95275 capture, so the V2 merge changed nothing the golden compares. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds the hand-off instructions, the Plan limits section, the budget and hand-off repair hints and the degrade path, all only when the request offers workflow_handoff, plus the gated deliverables fact and recipe. The off-golden harness now uses the real correlation helper so it holds when an earlier suite imported the planner under a stand-in logger. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds functional tests for the hand-off planner guidance: the Plan limits section, the budget repair round, the answer steer, the drop report and the manual approval floor. Plan-limit lines now use singular nouns for a count of one. Fixes the builder test header order. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The executor deep-copies the adapter's workflow_handoff sidecar into the server step record, restores it on recovery like the proposal, run and results sidecars, and leaves it out of the public step record and the stream frames. The finalize note covers a prepared or refused hand-off, and the services alias discovery skips hand-off steps. Adds the adapter, capability and fresh-process import tests. Refs #1549 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A structured (v3) run is still refused unless it is a chat hand-off run whose single workflow output is the report node's text output; that report is read through the node-level result store, which re-proves the run's definition identity. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Add the requester-only workflow-handoffs route family: status, draft, accept and deny. - Accept re-authorizes, rebuilds and revalidates the blueprint, claims the decision with the run's CAS model, creates the one-time workflow disabled and queues exactly one durable run with chat delivery. A created-but-not-queued retry reuses the same workflow and run IDs. - Refuse a hand-off on Phase 4's accept and a Phase 4 proposal on the hand-off accept. - Phase 5's workflow_run refuses a one-time workflow with the closed workflow_one_time reason. - Share the chat delivery seed between Phase 5 and the hand-off queue. - Exclude hand-offs from Phase 4's cap and add the rolling daily hand-off count. - Add route, route-policy, import and Phase 5 refusal tests. Refs #1549 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pins the one_time origin marker outside the revision and fingerprint, refuses an edited one-time workflow in Phase 5, proves a create that loses the race returns the stored hand-off, and checks edited hand-off parity with a save. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
On every terminal transition of the run a one-time workflow was handed off
for, the runtime projection now writes one_time_status {state, run_id,
completed_at} beside the other runtime fields. It stays outside the
definition revision and the execution fingerprint, never changes
modified_at or is_enabled, and a failure while computing it is logged
content-free and never stops the run from being finalized.
The lifecycle test pins every terminal state, the failing-write path, the
runs that record nothing, a resumed run, editor saves, the query-over-limit
pause the deadline does not expire, and how a disabled hand-off ends in chat
through 6b-1.
Refs #1549
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives an accepted hand-off over 3 and 200 workspace documents through the real durable runtime, workflow runner, result store, reporting, result reader and 6b-1 delivery worker, faking only the containers, the model, the per-document Analyze, document enumeration and the lease heartbeat. Every app module's log_event is recorded, and no log carries a private value or an internal hand-off ID. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Commit ce16933 stored functions_workflow_chat_delivery.py, functions_workflow_reporting.py and the hand-off end-to-end test with CRLF line endings. The two production files now match V2 byte for byte again, and the test keeps only its comment change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
SaveParityHarness loads the real workflow stores with test doubles installed in sys.modules. Its _installed() helper put back only the names it listed, so a module the real stores imported in turn, such as functions_workflow_chat_delivery, stayed in sys.modules bound to the doubles, and later tests in the same pytest process imported it. _restored_modules() now undoes every sys.modules change made while loading. Two guard tests in test_workflow_draft_service.py fail if building or calling a harness leaves any module entry changed, or if a module imported before a harness ran is not put back. The pollution exists on the base. On 15f1e50 the combined pytest run fails 8 tests in both pytest and pytest -O: the 4 known proposal_editor_round_trip failures plus 4 caused by the leaked modules (2 hand-off routes tests, 1 lifecycle test and test_workflow_loop_schema.py::test_group_iterable_scope_is_bound_to_the_server_normalization_context). With this change only the 4 base failures remain (1489 passed). The standalone python and python -O runs pass before and after. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds the feature doc, the admin settings and limits rows and troubleshooting entries, the release notes entry, the features.yml setting key and the regenerated docs inventory. test_docs_app_surface_coverage.py 7/7 and test_docs_site_quality.py 6/6 pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds four tests: a one-time workflow at a proposal's id is never taken for the proposal's workflow (3b); an accept creates the disclosed bound and refuses a loop limit lowered below it (57b); a one-time record is adopted only as this user's hand-off from chat (49b); an edit may not add a hosted agent outside the loop (55). builder+routes and routes+builder 93 passed and harness8 285 passed under pytest and pytest -O; the combined hand-off, phase 4/5 and loop-schema run shows only the 4 base proposal_editor_round_trip failures (1493 passed). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sets config.py VERSION to 0.261.238, one above the versions claimed by the open V2 PRs (#1636 0.261.234, #1637 0.261.235, #1638 0.261.236, 6b-2 next), and moves the hand-off modules' and tests' Version, Implemented in, MINIMUM_VERSION and assert_app_version_at_least literals to it. The off-golden fixture keeps its factual capture note (f1aeef1, 0.261.233). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| """Read the rolling 24-hour hand-off limit; a corrupt stored value fails closed.""" | ||
| if settings is None: | ||
| # Settings initialize application clients; load them only at a request boundary. | ||
| from functions_settings import get_settings |
| _require("Prepared a workflow hand-off." in " ".join(_messages(calls, logging.INFO)), "The step must log.") | ||
| _assert_logs_clean(calls, sidecar) | ||
|
|
||
| calls = _install(monkeypatch, ho, drafts, outcome={"ok": False, "errors": [{"code": "scope_unavailable"}]}) |
| touched = [] | ||
|
|
||
| class _Forbidden(types.ModuleType): | ||
| def __getattr__(self, name): |
| SECRET_REFERENCE = {"id": "secret", "name": "secret", "document_id": "forbidden-doc", "scope_type": "personal"} | ||
| RACE_CREATES = { | ||
| "blueprint": lambda harness, payload: _create(harness, DOCS, DOC_HANDLES), | ||
| "payload": lambda harness, payload: _create_from_payload(harness, payload), |
V2 moved to efee707 (VERSION 0.261.234) when #1635 merged: saved-result error messages by family and repaired workflow test harnesses. Conflicts: - config.py: keep this branch's 0.261.238, still above V2's 0.261.234 and the claimed 0.261.235 to 0.261.237. - release_notes.md: keep both sections, 0.261.238 above 0.261.234. test_workflow_draft_save_parity.py auto-merged: #1635 adds the chat delivery contract to REAL_MODULES, and this branch's _restored_modules still undoes every sys.modules change made while the harness loads. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
#1635 loads the chat delivery contract as a real module, so it no longer reaches the harness only as a transitive import. The comment now describes what _restored_modules undoes without naming that module. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 moved to 5be1d84 (VERSION 0.261.235) when #1636 merged: must-acknowledge workflow alerts, alert sounds, sizes and team delivery. Conflicts: - config.py: keep this branch's 0.261.238, still above V2's 0.261.235. - release_notes.md: keep both sections, 0.261.238 above 0.261.235. This also restores V2's 0.261.234 entry exactly: the previous merge (ab87e79) left extra spaces in its Ref line and dropped the blank line before 0.261.233. admin_settings_fields.py, functions_personal_workflows.py, functions_settings.py, route_frontend_admin_settings.py and docs/_data/features.yml auto-merged. docs/_data/app_surface.yml auto-merged with capabilities: 125, because both sides raised the same count by one. Regenerated with scripts/build_docs_inventory.py: 126, for enable_chat_orchestration_workflow_handoff and enable_workflow_alert_sounds. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The adapter test was the only new hand-off test file without one, so its python and python -O runs executed nothing. It now runs its tests through pytest.main, like the other hand-off test files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The V2 admin renderer evaluates each field's conditions on their own, so a field gated on another switch must repeat that switch's conditions. The daily limit was gated on the hand-off switch but not on the switch's own allow_user_workflows gate, so it could stay on screen while the switch was hidden. Caught by test_v2_admin_settings_schema.py's test_gated_fields_inherit_their_gate_s_own_conditions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 moved to 25470e7 (VERSION 0.261.236) when #1643 merged: the V2 chat rail scrolls as one panel, and workflow alerts pop up from the bell. Conflicts: - config.py: keep this branch's 0.261.238, still above V2's 0.261.236. - release_notes.md: keep both sections, 0.261.238 above 0.261.236. #1643 shares no other file with this branch. Apart from config.py, the merged tree differs from ac9e524 only in #1643's 15 files, each identical to V2's. docs/_data/app_surface.yml is unchanged after re-running scripts/build_docs_inventory.py. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 moved to 4f3eacb (VERSION 0.261.238) when #1644 merged: the orchestration settings store returns plain dicts, which fixes the settings race, and Microsoft 365 actions now work as chat orchestration plan steps. Conflicts: - release_notes.md: keep both sections. This branch's hand-off section sits above #1644's; both are headed 0.261.238 until the renumber commit that follows moves this branch to 0.261.239. config.py auto-merged: both sides were at 0.261.238. functions_orchestration_adapters.py, functions_orchestration_schema.py and docs/admin/orchestration.md auto-merged; each side's changed lines are kept unchanged. The merged tree differs from 6fdef66 only in #1644's files; every file outside those three and the release notes is identical to V2's. docs/_data/app_surface.yml is unchanged after re-running scripts/build_docs_inventory.py. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 reached 0.261.238 when #1644 merged, so this branch moves one above it. Sets config.py VERSION to 0.261.239 and moves this branch's own 0.261.238 lines to 0.261.239: the hand-off modules' and tests' Version, Implemented in, MINIMUM_VERSION and assert_app_version_at_least literals, the two route-test coverage notes, the run adapter test's hand-off refusal note, the two hand-off rows in docs/admin/orchestration.md, the feature doc and this branch's release-notes header. #1644's own 0.261.238 lines (its schema docstring, its Microsoft 365 section and troubleshooting row in orchestration.md, its fix docs and its release-notes section) are unchanged. The open V2 PRs claim 0.261.234 (#1639), 0.261.235 (#1637), 0.261.236 (#1638) and 0.261.246 (#1641), none of them 0.261.239. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Merge only landed V2 with rerere disabled. Preserve both capability-import sets, both draft error families, all incoming release-note sections and the three V2 off-golden fixtures. Keep V2 VERSION 0.261.248 pending the separate hand-off renumber to 0.261.250. Auto-merged shared files preserve both sides; executor registry imports are their exact union. The regenerated docs inventory is unchanged. Full verification remains pending. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 reached 0.261.248 with #1641; coordinator reserves 0.261.249 for 6b-2 and 0.261.250 for Phase 7a. Change only config.py VERSION and the 44 PR-owned version lines. Preserve every incoming V2 version reference and golden fixture. No behavior changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
marked this pull request as ready for review
October 6, 2026 01:43
Paul Lizer (paullizer)
merged commit Oct 6, 2026
15feec6
into
paullizer-react-v2-ui
11 checks passed
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Oct 6, 2026
Take V2's config.py (0.261.250) and keep 6b-2's release-note section above V2's 0.261.250 section with every V2 byte preserved. The renumber to 0.261.251 follows separately. 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 6, 2026
Keep all base version history; update only 6b-2's version, tests and documentation after the V2 microsoft#1640 merge, which took 0.261.250: config.py moves from 0.261.250 and 51 lines in 21 other files move from 0.261.249, all to 0.261.251. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Oct 6, 2026
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Oct 6, 2026
The hand-off branch of read_workflow_result loaded the report manifest by its exact node selectors and checked its identity, text output and completion, but never walked its lineage. A report whose consumed-input receipts were malformed, or named a missing or corrupt parent, still read as available, although the general path refuses the same lineage. _read_handoff_result now calls the shared node lineage authorizer (authorize_workflow_node_result_read) on the saved workflow, seeded with the loaded manifest and the same memoized loader, before the result is described or excerpted. Sources aren't re-resolved. read_workflow_result passes through the user id it has already proved. The reader tests add a strong valid control whose report consumed a real collect records result, built through the runner's result contract, and cover a malformed receipt (the repro), a missing parent, a parent whose output disagrees with the receipt, a parent whose bytes don't match its hash, and edited or re-enabled definitions with lineage. A descriptor-only read walks the lineage but reads no section. Refs microsoft#1549, microsoft#1543. Follow-up to microsoft#1640. 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 6, 2026
Add the Workflow Hand-off Result Lineage Fix doc, covering the reader's missing lineage walk, the change and its failure codes, the recaptured off-golden, validation and limitations. In the hand-off feature doc, the reader now re-proves the report's lineage with the shared node lineage authorizer, an edited or re-enabled workflow still fails closed with workflow_result_invalid, and the test, file and performance notes match. Add a 0.261.252 release-notes section with two bug fixes. Existing sections are unchanged, and the docs inventory regenerates unchanged. Part of microsoft#1549. Part of microsoft#1543. Follow-up to microsoft#1640. 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 6, 2026
test_the_phase_4_blueprint_and_payloads_are_unchanged failed at V2 15feec6 on the schema digest alone. Its pin (ab12fb1d) predated microsoft#1641, which added the merge task schema and was merged into Phase 7a at b8e4730 without a refresh. The pin now holds 520139cb, the value V2 computes at a5a5b1c, after microsoft#1641 and before hand-off merged, so the test still proves hand-off left the Phase 4 schema unchanged. The payload, validation and dry-run pins are unchanged. The fix doc adds the builder test to Files modified, records the pin under Validation, and gives the 200-document read time as about 1.7 seconds, to match the c07b911 timings. Refs microsoft#1549, microsoft#1543. Follow-up to microsoft#1640. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
added a commit
that referenced
this pull request
Oct 6, 2026
…w-up - Status date 2026-10-06, with paullizer-react-v2-ui at 0.261.252. - Phase 6: 6b-2 done in #1639 (v0.261.251), with how it shipped. - Phase 7: 7a done in #1640 (v0.261.250) and its lineage follow-up in #1647 (v0.261.252); 7b, the V2 hand-off card, is next. Admins should leave the hand-off setting off until 7b ships. - Section 9: the run deep-link and one-time workflow decisions are settled. - Release-notes integrity follow-up updated to 187 missing releases. Docs only; no version bump. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Oct 6, 2026
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
Phase 7a, the server half of one-time workflow hand-off (#1549, part of #1543). When a chat request is too big for one plan (more documents, steps or time than a plan allows), the planner can now hand it to a one-time durable workflow instead of failing on its budget or giving a partial answer.
workflow_handoff("Hand off large work"): Reason role, manual approval floor, at most one per plan. Its step dry-runs a static blueprint (a loop over named documents or a workspace query, one review task and one report task) into adefinition_version3 For each workflow. It keeps a server-only sidecar on the step record and returns a hand-off card. Nothing runs until the user approves the plan and then the card.origin.one_time: true, written only by the server) and queues exactly one durable run. The workflow id and the run's request id both derive from the hand-off, so a retry, a second tab or a crash finds the same workflow and run. When the run ends, the workflow records how inone_time_status, and 6b-1's delivery posts the report back into the conversation.enable_chat_orchestration_workflow_handoff) is off by default. Hand-offs from chat per user per day (chat_orchestration_max_workflow_handoffs_per_day) takes 1–100 and defaults to 5. Hand-off also needs Chat Orchestration, personal workflows, Propose Workflows From Chat, Run Workflows From Chat and Use Workflow Results In Chat, and works only in the user's own private conversations. With the setting off, or on with any other gate closed, planning and the Phase 4 routes are byte-identical to V2; the off-golden checks this.application/v2_ui/changes. The V2 hand-off card is 7b, a separate PR. Until it ships, users can't accept a hand-off in the browser, so the setting should stay off.Full design, contract and limits:
docs/explanation/features/CHAT_ORCHESTRATION_WORKFLOW_HANDOFF.md.Linked issue
Refs #1549 (Phase 7a; 7b, the V2 card, follows in a separate PR). Part of #1543.
Release Notes & Latest Features
Is this visible to end users?
Is this admin-facing (Admin Settings, governance, deployment, config)?
Should this become a Latest Feature card?
Screenshot needed for the card?
Version bump
application/single_app/config.pyVERSIONthird segment bumped from 0.261.233 (V2) to 0.261.238, in its own renumber commit (d2af803). The open V2 PRs claim .234–.236, and 6b-2 (Phase 6b-2: show chat-started workflow runs and their posted results in V2 #1639) is expected to land first at .237. If V2 passes .237 before this merges, I'll renumber again in a separate commit.deployers/version.txtnot needed:deployers/isn't changed.Testing / validation
Head d2af803. Base: unmodified V2 f1aeef1 (0.261.233). This branch has merged it and is 0 commits behind.
Everything ran on Windows with Python 3.12, from the repo root, with
PYTHONPATH=application/single_app;functional_tests. A localsitecustomize.pywas also on the path. It only stops Python'splatformmodule from querying WMI, which hangs on this host, and it touches neither the app nor the tests. Each mode below is one of these commands:python -m pytest -p no:cacheprovider -q --no-header -rfE --tb=line functional_tests/<file> [more files]python -O -m pytest -p no:cacheprovider -q --no-header -rfE --tb=line functional_tests/<file> [more files]python functional_tests/<file>, the file's own__main__runnerpython -O functional_tests/<file>4-mode table
test_orchestration_workflow_handoff_adapter.pytest_orchestration_workflow_handoff_capability.pytest_orchestration_workflow_handoff_imports.pytest_orchestration_workflow_handoff_off_golden.pytest_orchestration_workflow_handoff_planner.pytest_orchestration_workflow_handoff_routes.pytest_workflow_handoff_builder.pytest_workflow_handoff_end_to_end.pytest_workflow_handoff_lifecycle.pytest_workflow_handoff_origin.pytest_workflow_handoff_result_reader.pyroute_tests/test_route_blueprint_policy_inventory.pyroute_tests/test_route_unauthenticated_policy_contract.pypytest rows run their files in one process.
builder+routesandroutes+builderrun the builder and routes tests together in both orders.harness8runs the 8 files that share the draft save-parity harness.hand-off 11 + touched 9runs the 11 new hand-off test files with the 8 existing test files this PR edits and the route-policy coverage test. File rows run each file standalone.okmeans the file's own runner passed without printing a pytest summary.One process, everything together
Still running. The only failures expected are the 4 base failures explained in the next paragraph.
The 4 failures are the base's
test_orchestration_workflow_proposal_editor_round_trip.pyfailures. That test drives the V2 TypeScript editor through Node and asserts thatapplication/v2_ui/node_modulesexists, which isn't installed in these checkouts. On f1aeef1 the same 4 tests fail under pytest, pytest -O, python and python -O.Extra reruns: A5, A13, Phase 4 and Phase 5
test_workflow_loop_reporting.pytest_orchestration_actions_admin.pytest_orchestration_workflow_proposal_done_when.pytest_orchestration_workflow_proposal_editor_round_trip.pytest_orchestration_workflow_proposal_imports.pytest_orchestration_workflow_proposal_routes.pytest_orchestration_workflow_proposals_admin.pytest_orchestration_workflow_propose_capability.pytest_orchestration_workflow_run_adapter.pytest_orchestration_workflow_run_answer.pytest_orchestration_workflow_run_approval_floor.pytest_orchestration_workflow_run_capability.pytest_orchestration_workflow_run_effects.pytest_orchestration_workflow_run_imports.pytest_orchestration_workflow_run_link_routes.pytest_orchestration_workflow_run_planning_context.pytest_orchestration_workflow_runs_admin.pytest_orchestration_workflow_runs_off_golden.pytest_v2_orchestration_workflow_run_links_xss_guardrail.pytest_workflow_loop_schema.pytest_workflow_loop_reporting.py(A13) andtest_orchestration_actions_admin.py(A5's admin pin) run in all four modes. The 18 Phase 4 and Phase 5 files run standalone here; their pytest runs are in the one-process run above and the base comparison below.test_orchestration_workflow_proposal_editor_round_trip.py's 4 failures are the base failures described above.Base comparison, file by file
Each file ran in its own pytest process on the head and on f1aeef1. The head has no new failures: every head failure also fails on the base, with the same test id.
test_orchestration_workflow_proposal_editor_round_trip.pytest_orchestration_plan_revision_references.pytest_orchestration_plan_revision_routes.pytest_workflow_runtime_integration.pytest_admin_settings_group_shared_regions.pytest_admin_settings_nav_map.pytest_admin_settings_safe_int_fallback_fix.pytest_admin_settings_sidebar_card_parity.pytest_admin_settings_tab_preservation.pytest_app_settings_cache_versioning.py74 files pass on both sides
test_m365_run_as_self_authored.py: 51 passed (rc 0) at the head; 51 passed (rc 0) on the basetest_workflow_assist_dry_run_parity.py: 16 passed (rc 0) at the head; 16 passed (rc 0) on the basetest_workflow_draft_save_parity.py: 26 passed (rc 0) at the head; 26 passed (rc 0) on the basetest_workflow_draft_service.py: 40 passed (rc 0) at the head; 38 passed (rc 0) on the basetest_workflow_draft_v2_round_trip.py: 19 passed (rc 0) at the head; 19 passed (rc 0) on the basetest_workflow_handoff_builder.py: 32 passed (rc 0) at the head; absent on the basetest_workflow_handoff_origin.py: 34 passed (rc 0) at the head; absent on the basetest_workflow_origin_provenance.py: 67 passed (rc 0) at the head; 67 passed (rc 0) on the basetest_orchestration_workflow_proposal_done_when.py: 2 passed (rc 0) at the head; 2 passed (rc 0) on the basetest_orchestration_workflow_proposal_imports.py: 6 passed (rc 0) at the head; 6 passed (rc 0) on the basetest_orchestration_workflow_proposal_routes.py: 60 passed (rc 0) at the head; 60 passed (rc 0) on the basetest_orchestration_workflow_proposals_admin.py: 12 passed (rc 0) at the head; 12 passed (rc 0) on the basetest_orchestration_workflow_propose_capability.py: 88 passed (rc 0) at the head; 88 passed (rc 0) on the basetest_orchestration_workflow_run_adapter.py: 137 passed (rc 0) at the head; 129 passed (rc 0) on the basetest_orchestration_workflow_run_answer.py: 18 passed (rc 0) at the head; 18 passed (rc 0) on the basetest_orchestration_workflow_run_approval_floor.py: 39 passed (rc 0) at the head; 39 passed (rc 0) on the basetest_orchestration_workflow_run_capability.py: 79 passed (rc 0) at the head; 79 passed (rc 0) on the basetest_orchestration_workflow_run_effects.py: 10 passed (rc 0) at the head; 10 passed (rc 0) on the basetest_orchestration_workflow_run_imports.py: 13 passed (rc 0) at the head; 13 passed (rc 0) on the basetest_orchestration_workflow_run_link_routes.py: 63 passed (rc 0) at the head; 63 passed (rc 0) on the basetest_orchestration_workflow_run_planning_context.py: 80 passed (rc 0) at the head; 80 passed (rc 0) on the basetest_orchestration_workflow_runs_admin.py: 14 passed (rc 0) at the head; 14 passed (rc 0) on the basetest_orchestration_workflow_runs_off_golden.py: 5 passed (rc 0) at the head; 5 passed (rc 0) on the basetest_v2_orchestration_workflow_run_links_xss_guardrail.py: 4 passed (rc 0) at the head; 4 passed (rc 0) on the basetest_workflow_loop_schema.py: 212 passed (rc 0) at the head; 212 passed (rc 0) on the basetest_orchestration_workflow_results_aliases.py: 10 passed (rc 0) at the head; 10 passed (rc 0) on the basetest_orchestration_workflow_results_answer.py: 18 passed (rc 0) at the head; 18 passed (rc 0) on the basetest_orchestration_workflow_results_capability.py: 70 passed (rc 0) at the head; 70 passed (rc 0) on the basetest_orchestration_workflow_results_imports.py: 26 passed (rc 0) at the head; 26 passed (rc 0) on the basetest_orchestration_workflow_results_off_golden.py: 5 passed (rc 0) at the head; 5 passed (rc 0) on the basetest_orchestration_workflow_results_reads.py: 53 passed (rc 0) at the head; 53 passed (rc 0) on the basetest_orchestration_workflow_results_time_zone.py: 8 passed (rc 0) at the head; 8 passed (rc 0) on the basetest_orchestration_workflow_deliverable.py: 26 passed (rc 0) at the head; 26 passed (rc 0) on the basetest_workflow_chat_delivery_concurrency.py: 5 passed (rc 0) at the head; 5 passed (rc 0) on the basetest_workflow_chat_delivery_contract.py: 72 passed (rc 0) at the head; 72 passed (rc 0) on the basetest_workflow_chat_delivery_control_pins.py: 35 passed (rc 0) at the head; 35 passed (rc 0) on the basetest_workflow_chat_delivery_imports.py: 31 passed (rc 0) at the head; 31 passed (rc 0) on the basetest_workflow_chat_delivery_loop.py: 9 passed (rc 0) at the head; 9 passed (rc 0) on the basetest_workflow_chat_delivery_notice_and_unread.py: 25 passed (rc 0) at the head; 25 passed (rc 0) on the basetest_workflow_chat_delivery_off_golden.py: 6 passed (rc 0) at the head; 6 passed (rc 0) on the basetest_workflow_chat_delivery_placement_and_masking.py: 8 passed (rc 0) at the head; 8 passed (rc 0) on the basetest_workflow_chat_delivery_projection_and_seed.py: 16 passed (rc 0) at the head; 16 passed (rc 0) on the basetest_workflow_chat_delivery_refusals.py: 2 passed (rc 0) at the head; 2 passed (rc 0) on the basetest_workflow_chat_delivery_save_guard.py: 14 passed (rc 0) at the head; 14 passed (rc 0) on the basetest_workflow_chat_delivery_status_route.py: 48 passed (rc 0) at the head; 48 passed (rc 0) on the basetest_workflow_chat_delivery_worker.py: 112 passed (rc 0) at the head; 112 passed (rc 0) on the basetest_orchestration_plan_revision_planner.py: 11 passed (rc 0) at the head; 11 passed (rc 0) on the basetest_orchestration_plan_revision_store.py: 44 passed (rc 0) at the head; 44 passed (rc 0) on the basetest_orchestration_failure_telemetry.py: 18 passed (rc 0) at the head; 18 passed (rc 0) on the basetest_orchestration_actions_admin.py: 8 passed (rc 0) at the head; 8 passed (rc 0) on the basetest_admin_settings_absent_field_preservation.py: 3 passed (rc 0) at the head; 3 passed (rc 0) on the basetest_admin_settings_dependencies.py: 4 passed (rc 0) at the head; 4 passed (rc 0) on the basetest_admin_settings_diagnostics_routes.py: 4 passed (rc 0) at the head; 4 passed (rc 0) on the basetest_admin_settings_field_contract.py: 2 passed (rc 0) at the head; 2 passed (rc 0) on the basetest_admin_settings_fields_registry_integrity.py: 5 passed (rc 0) at the head; 5 passed (rc 0) on the basetest_admin_settings_modal_placement.py: 2 passed (rc 0) at the head; 2 passed (rc 0) on the basetest_admin_settings_pane_variable_scope.py: 5 passed (rc 0) at the head; 5 passed (rc 0) on the basetest_admin_settings_sidebar_and_agent_catalog_guard.py: 2 passed (rc 0) at the head; 2 passed (rc 0) on the basetest_admin_settings_template_composition.py: 5 passed (rc 0) at the head; 5 passed (rc 0) on the basetest_admin_settings_walkthrough_targets.py: 3 passed (rc 0) at the head; 3 passed (rc 0) on the baseroute_tests/test_route_blueprint_policy_inventory.py: 12 passed (rc 0) at the head; 11 passed (rc 0) on the baseroute_tests/test_route_policy_test_coverage.py: 2 passed (rc 0) at the head; 2 passed (rc 0) on the baseroute_tests/test_route_unauthenticated_policy_contract.py: 7 passed (rc 0) at the head; 6 passed (rc 0) on the baseroute_tests/test_workflow_alert_since_hours_policy.py: 18 passed (rc 0) at the head; 18 passed (rc 0) on the baseroute_tests/test_workflow_assist_policy.py: 64 passed (rc 0) at the head; 64 passed (rc 0) on the baseroute_tests/test_workflow_execution_journal_policy.py: 3 passed (rc 0) at the head; 3 passed (rc 0) on the baseroute_tests/test_workflow_flow_inspection_policy.py: 22 passed (rc 0) at the head; 22 passed (rc 0) on the baseroute_tests/test_workflow_loop_policy.py: 3 passed (rc 0) at the head; 3 passed (rc 0) on the baseroute_tests/test_workflow_repeat_policy.py: 2 passed (rc 0) at the head; 2 passed (rc 0) on the baseroute_tests/test_workflow_result_context_policy.py: 38 passed (rc 0) at the head; 38 passed (rc 0) on the basetest_orchestration_dependency_allowlist.py: 41 passed (rc 0) at the head; 41 passed (rc 0) on the basetest_workflow_orchestration_limits_settings.py: 28 passed (rc 0) at the head; 28 passed (rc 0) on the basetest_docs_app_surface_coverage.py: 7 passed (rc 0) at the head; 7 passed (rc 0) on the basetest_docs_site_quality.py: 6 passed (rc 0) at the head; 6 passed (rc 0) on the baseAbsent on the base because this PR adds them:
test_workflow_handoff_builder.py,test_workflow_handoff_origin.py.Why these files fail on the base:
test_orchestration_plan_revision_references.py(24) andtest_orchestration_plan_revision_routes.py(29): every test stops onImportError: cannot import name 'enabled_required' from 'functions_settings'.test_workflow_runtime_integration.py(4): the background run endsfailedwhere the tests expectcompleted(3) orwaiting_approval(1).test_orchestration_workflow_proposal_editor_round_trip.py(4): it needsapplication/v2_ui/node_modules, which isn't installed in these checkouts.5 admin-settings files and
test_app_settings_cache_versioning.py(1 failure each):inbound-mcp-configurationandsupport_latest_features_settingstwice.content-screeningtab with no pane, and anm365-retrieval-configurationcard with no sidebar destination.VERSION = "0.240.002". Another looks for afunctions_settings.pycall that V2 no longer has.test_admin_settings_tab_preservation.pyreturnsFalse, which the pytest reports functional tests that return False as passed #1572 guard fails.This PR touches
admin_settings_fields.py,functions_settings.py,route_frontend_admin_settings.pyand the Chat Orchestration pane template, so I re-ran these 6 failing tests on both SHAs. Their assertion messages are identical.End to end (A6)
test_workflow_handoff_end_to_end.pyaccepts a hand-off over a workspace query of 3 documents and of 200, then runs it through the real durable runtime, workflow runner, For each, Collect, report paging, and 6b-1's delivery worker and result reader. Only Cosmos, the model, the per-document Analyze, document enumeration and the lease heartbeat are faked.No fallback was needed: both legs run the full count in all four modes. The 200-item leg checks the A13 bounds on the real reporting path:
MAX_REPORT_PAGE_RECORDS(100) records andANALYSIS_RECORD_PAGE_BYTES(128 KiB);MAX_REDUCTION_CHILDRENchildren.It also sweeps every response and app log, from accept through delivery, for private values and internal hand-off ids.
Off-golden (A1, A18)
test_orchestration_workflow_handoff_off_golden.pycompares planning and the Phase 4 routes against a fixture written with--write-goldenon unmodified V2. It covers the setting off, and the setting on with another gate off. The fixture was captured on fd842e1, then re-captured on ca95275 (#1630), f7e268c and f1aeef1 (08d28e2). Each re-capture differed only in itscaptured_fromlabel; none was hand-edited. No other golden fixture changes in this diff, and every existing golden passes unchanged at the head and on f1aeef1. Phase 4's setting off-golden (9 passed), the reference-authorizer golden (2 tests, 74 subtests) and the run-prompt golden intest_workflow_run_time_context.py(13 passed) pass in all four modes on both sides. Per file under pytest: Phase 5's runs (5 passed (rc 0) at the head), 6a's results (5 passed (rc 0) at the head), 6b-1's chat delivery (6 passed (rc 0) at the head).Mutation testing
The 84 mutants are plan §19's list, including the ones approval notes A7 and A11–A17 added. Each is applied alone to a production file under
application/single_app/. Its target tests then run twice: once withpython -m pytest <tests> -x -q -p no:cacheprovider --rootdir functional_tests -W ignore("normal" in the table) and once with the same command underpython -O("-O"). Every kill below is a named test failing with exit code 1, never a collection error. 82 of 84 are killed in both modes. The 2 survivors, 50 and 51, are equivalent:actor_user_id=None): every use inbuild_personal_workflow_documentgoes throughmodifying_user_id = actor_user_id or user_id, so the requester is still the actor andmodified_by._requests_url_accessrefuses URL Access before the build.Four mutants didn't die on the first pass: 57b, 3b, 55 and 49b. 49b's first-pass kill was false, caused by the harness pollution that bacbf0a fixes. Each of the four now dies to a new test from f31d45f.
Kill table (84 mutants)
test_orchestration_workflow_handoff_routes::test_only_the_requester_can_read_or_decide_a_handofftest_orchestration_workflow_handoff_routes::test_only_the_requester_can_read_or_decide_a_handofftest_orchestration_workflow_handoff_routes::test_phase_4_refuses_a_handoff_and_a_handoff_route_refuses_a_proposaltest_orchestration_workflow_handoff_routes::test_phase_4_refuses_a_handoff_and_a_handoff_route_refuses_a_proposaltest_orchestration_workflow_handoff_routes::test_a_one_time_workflow_at_a_proposals_id_is_never_taken_for_the_proposals_workflowtest_workflow_handoff_builder::test_named_documents_build_a_durable_version_3_for_eachtest_workflow_handoff_builder::test_an_edited_hand_off_stays_a_paused_manual_durable_workflowtest_workflow_handoff_builder::test_the_builder_is_deterministic_and_boundedtest_orchestration_workflow_handoff_routes::test_accept_creates_the_workflow_disabled_and_queues_exactly_one_durable_runtest_orchestration_workflow_handoff_routes::test_accept_creates_the_workflow_disabled_and_queues_exactly_one_durable_runtest_orchestration_workflow_handoff_routes::test_a_create_runs_under_a_claim_that_is_released_when_it_failstest_orchestration_workflow_handoff_routes::test_a_create_runs_under_a_claim_that_is_released_when_it_failstest_workflow_handoff_builder::test_a_hand_off_is_created_once_paused_and_one_timetest_orchestration_workflow_handoff_routes::test_an_accept_whose_decisions_were_not_recorded_still_settles_on_one_runtest_orchestration_workflow_handoff_routes::test_a_handoff_does_not_count_toward_phase_4s_workflow_captest_orchestration_workflow_handoff_routes::test_the_daily_limit_counts_recent_handoffs_including_ones_being_deletedtest_orchestration_workflow_handoff_routes::test_the_decision_field_claim_window_and_request_ids_are_the_hand_offs_owntest_workflow_handoff_builder::test_alerts_are_notify_only_on_every_run_at_info_severitytest_workflow_handoff_builder::test_named_documents_build_a_durable_version_3_for_eachtest_workflow_handoff_builder::test_an_edited_hand_off_stays_a_paused_manual_durable_workflowtest_workflow_handoff_builder::test_named_documents_build_a_durable_version_3_for_eachtest_orchestration_workflow_handoff_planner::test_a_request_that_cannot_hand_off_sees_no_hand_off_guidance[absent-all_on]test_orchestration_workflow_handoff_planner::test_a_request_that_cannot_hand_off_sees_no_hand_off_guidance[absent-all_on]test_orchestration_workflow_handoff_off_golden::test_planning_is_byte_identical_when_handoff_is_unavailable[absent]test_orchestration_workflow_handoff_adapter::test_the_step_record_keeps_the_sidecar_and_the_public_record_drops_ittest_orchestration_workflow_handoff_adapter::test_the_step_keeps_its_sidecar_on_the_server_and_retains_only_the_cardtest_orchestration_workflow_handoff_adapter::test_the_step_keeps_its_sidecar_on_the_server_and_retains_only_the_cardtest_workflow_handoff_lifecycle::test_each_terminal_state_records_how_the_hand_off_ended[cancelled]test_workflow_handoff_lifecycle::test_a_failing_status_write_never_breaks_finalizationtest_orchestration_workflow_handoff_routes::test_accept_creates_the_workflow_disabled_and_queues_exactly_one_durable_runtest_orchestration_workflow_handoff_routes::test_accept_creates_the_workflow_disabled_and_queues_exactly_one_durable_runtest_orchestration_workflow_handoff_routes::test_accept_creates_the_workflow_disabled_and_queues_exactly_one_durable_runtest_orchestration_workflow_handoff_routes::test_accept_creates_the_workflow_disabled_and_queues_exactly_one_durable_runtest_orchestration_workflow_handoff_capability::test_the_descriptor_is_a_reason_step_with_a_manual_floor_and_one_step_per_plantest_orchestration_workflow_handoff_capability::test_the_descriptor_is_a_reason_step_with_a_manual_floor_and_one_step_per_plantest_orchestration_workflow_handoff_capability::test_the_descriptor_is_a_reason_step_with_a_manual_floor_and_one_step_per_plantest_orchestration_workflow_handoff_adapter::test_the_module_imports_no_flask_store_or_runtimetest_orchestration_workflow_handoff_adapter::test_the_module_imports_no_flask_store_or_runtimetest_orchestration_workflow_handoff_routes::test_a_create_runs_under_a_claim_that_is_released_when_it_failstest_workflow_handoff_result_reader::test_every_other_structured_shape_stays_closed_with_no_read[trigger_manual]test_workflow_handoff_result_reader::test_two_outputs_or_another_handoff_stay_closedtest_workflow_handoff_result_reader::test_every_other_structured_shape_stays_closed_with_no_read[one_time_false]test_workflow_handoff_origin::test_a_chat_proposal_create_never_records_one_time[personal]test_orchestration_workflow_handoff_planner::test_a_budget_overflow_without_hand_off_fails_as_before[absent-all_on]test_orchestration_workflow_handoff_planner::test_a_budget_overflow_is_repaired_once_when_hand_off_is_offeredtest_orchestration_workflow_handoff_planner::test_a_budget_overflow_is_repaired_once_when_hand_off_is_offeredtest_orchestration_workflow_handoff_planner::test_an_offered_hand_off_adds_instructions_limits_and_its_own_catalog[on-all_on]test_orchestration_workflow_handoff_capability::test_a_request_that_cannot_hand_off_is_not_offered_it_and_gets_no_reason[on-orchestration_off]test_orchestration_workflow_handoff_off_golden::test_planning_is_byte_identical_when_handoff_is_unavailable[absent]test_orchestration_workflow_handoff_planner::test_an_offered_hand_off_adds_instructions_limits_and_its_own_catalog[on-all_on]test_orchestration_workflow_handoff_adapter::test_the_reply_notes_a_ready_hand_off_and_says_why_another_was_not_handed_offtest_orchestration_workflow_run_adapter::test_a_one_time_handoff_workflow_is_never_started_again_from_chattest_orchestration_workflow_handoff_routes::test_a_daily_count_that_cannot_be_read_fails_closedtest_workflow_handoff_origin::test_one_time_is_outside_the_revision_and_the_fingerprinttest_workflow_handoff_origin::test_one_time_is_outside_the_revision_and_the_fingerprinttest_workflow_handoff_origin::test_a_hand_off_and_a_chat_proposal_never_adopt_each_other[found]test_workflow_handoff_origin::test_an_editor_save_keeps_a_hand_off_one_time[2-lookalike]test_workflow_handoff_lifecycle::test_an_editor_save_keeps_the_stored_statustest_workflow_handoff_origin::test_a_material_change_marks_a_hand_off_edited_and_keeps_it_one_time[2]test_workflow_handoff_origin::test_a_save_route_create_never_records_one_time[personal-3]test_workflow_handoff_origin::test_a_chat_proposal_draft_never_records_one_timetest_orchestration_workflow_handoff_routes::test_only_the_requester_can_read_or_decide_a_handofftest_workflow_handoff_builder::test_a_create_that_loses_the_race_returns_the_stored_hand_off[blueprint]test_workflow_handoff_builder::test_a_hand_off_is_created_once_paused_and_one_timetest_orchestration_workflow_handoff_routes::test_a_workflow_at_the_handoff_id_that_is_not_one_time_is_refused_as_another_kindtest_workflow_handoff_builder::test_a_one_time_record_is_adopted_only_as_this_users_hand_off_from_chatmodified_by = actor_user_id or user_id(personal_workflows L896) still records the requester)test_workflow_handoff_builder::test_an_edited_hand_off_stays_a_paused_manual_durable_workflow_requests_url_accessrefuses that first)test_workflow_handoff_end_to_end::test_an_accepted_handoff_runs_once_and_posts_its_summary_to_chat[two-hundred-documents]test_orchestration_workflow_handoff_planner::test_a_hand_off_that_breaks_the_rules_twice_is_dropped_and_reportedtest_workflow_handoff_builder::test_only_local_agents_may_run_a_hand_offtest_workflow_handoff_builder::test_an_edit_may_not_add_a_hosted_agent_outside_the_looptest_workflow_handoff_builder::test_the_loop_limit_follows_the_admin_setting_up_to_the_hand_off_boundtest_workflow_handoff_builder::test_the_loop_limits_are_enforcedtest_orchestration_workflow_handoff_routes::test_an_accept_creates_the_disclosed_bound_and_refuses_a_limit_lowered_below_ittest_orchestration_workflow_handoff_adapter::test_the_reply_notes_a_ready_hand_off_and_says_why_another_was_not_handed_offtest_orchestration_workflow_handoff_adapter::test_the_reply_notes_a_ready_hand_off_and_says_why_another_was_not_handed_offtest_orchestration_workflow_handoff_adapter::test_the_reply_notes_a_ready_hand_off_and_says_why_another_was_not_handed_offtest_orchestration_workflow_handoff_planner::test_a_hand_off_that_breaks_the_rules_twice_is_dropped_and_reportedtest_orchestration_workflow_handoff_planner::test_a_model_cannot_report_a_dropped_hand_off[on]test_orchestration_workflow_handoff_planner::test_the_missing_answer_steer_is_only_for_a_plan_that_hands_work_offtest_workflow_handoff_end_to_end::test_an_accepted_handoff_runs_once_and_posts_its_summary_to_chat[three-documents]test_orchestration_workflow_handoff_adapter::test_the_step_keeps_its_sidecar_on_the_server_and_retains_only_the_card82 of 84 killed in both modes; the 2 survivors are equivalent. ¹ Re-run after the set B tests (f31d45f) were added.
Guardrails
Run with
run_v2_guardrails.py <worktree> f1aeef13d HEAD 7a, against the merged V2 base f1aeef1. All PASS..ts/.tsxfiles changed: 0@swagger_route(security=get_auth_security()))emptyandHandoffFixture, plussubprocessin the required fresh-process import tests.Decisions made autonomously / deviations
The coordinator approved each of these before I acted on it.
create_personal_workflow_if_absent, notsave_personal_workflow, which can't take an origin or a server-chosen id. This deviates from approval note 3.created_byandmodified_byare the requester. It uses the id derived from the hand-off, passes a server-built origin withone_time: True, and always overwritesis_enabledwithFalse.unsupported_field, a foreignidis refused, and any clientoriginorone_timeis stripped.handoff_kind_mismatch.test_only_starting_a_saved_workflow_sets_an_approval_floorchanges. Its floored set becomes{WORKFLOW_RUN, "workflow_handoff"}, withmanualfor both andNonefor every other descriptor.indivisible_record. 6b-1 posts its one failure note, and the saved records stay.agent_typecheck, and refuses withhandoff_agent_unsupported(422). At planning it lives in the hand-off-only part of the context, so the shared agent entries and the off-golden don't change. Mutants 53–55 cover it.handoff_effective_loop_limit(settings) = min(HANDOFF_MAX_LOOP_ITEMS, loop item limit). The context, the prompt, validation, the builder and the disclosure all use it.HANDOFF_MAX_LOOP_ITEMSis derived fromWORKFLOW_MAX_EXECUTION_ADMISSIONS: two admissions per item, three fixed, and a reserve of 997.handoff_limit_changed. The run keeps the disclosed N.deadline_exceeded, and 6b-1 treats it as expired.all_matchesquery that matches more than its limit pauses at the freeze, before any item is analyzed. The pause offers only Cancel, and the deadline doesn't expire it.cancelled,one_time_statusis written, and chat gets one "was cancelled" note.waiting/needs_youwithcancelallowed.POST /api/user/workflows/loop-inputs/previewaccepts, so 7b can show the match count before accept.final_response. The server addsworkflow_handoff_notein_finalizeon every finalize status.drop_workflow_handoffs,workflow_handoff_notesand "No workflow was handed off."missing_final_responserepair text also tells the planner not to answer the handed-off part now.Also approved at planning: A2, the server-only
origin.one_timemarker.workflow_runrefuses a one-time workflow withworkflow_one_time.Contract
Routes
All four routes are under
/api/v2/orchestration/runs/<run_id>/workflow-handoffs. Each one carries@swagger_route(security=get_auth_security()), needs a signed-in user with personal workflows (and theWorkflowUserrole when that's required), and re-checks every hand-off gate on each request. Anyone other than the requester getsrun_not_found.GET …/workflow-handoffs?conversation_id={run_id, handoffs: [{handoff_id, step_id, state, reason, created_at, expires_at, actions, summary, disclosure, workflow?, run?, chat_delivery?}]}POST …/workflow-handoffs/<handoff_id>/accept{conversation_id, mode, workflow?}{handoff_id, state: queued, created, workflow: {id, name, is_enabled}, run: {id, status}, chat_delivery}POST …/workflow-handoffs/<handoff_id>/deny{conversation_id}{handoff_id, state: denied}; idempotentGET …/workflow-handoffs/<handoff_id>/draft?conversation_id={handoff_id, workflow, url_access_note}; the user's own document and scope ids only, nevermodel_selectionor the handle mapErrors
There are 23 closed error codes, never
str(exc). The full table is in the feature doc's Errors section.handoff_kind_mismatch.proposal_kind_mismatch, and never take a one-time workflow for a proposal's. This is an additive hunk infunctions_orchestration_workflow_proposals.py, away fromproposal_statusand_workflow_projection(A9).state: created, and a retry queues the same run.Leak surface (plan §16)
model_selectionworkflow_handoff_decisions.workflow_handoff(the sidecar)handoff_id, name, summary, disclosure text and countsmodel_selectionscanmodel_selectionor the handle mapERROR_MESSAGES; neverstr(exc)log_eventwith codes, hashed identifiers, andsanitize_log_messagefor anything user-derivedchat_invocationholds the user's own conversation, run and step ids andhandoff_id, visible in their run history as in Phase 5.chat_deliverystays on the server (6b-1).Notes
functions_workflow_chat_delivery.py,functions_workflow_reporting.pyand the end-to-end test with CRLF line endings. The two production files match V2 byte for byte again and aren't in this diff.SaveParityHarnessintest_workflow_draft_save_parity.pyleft modules that the real stores import, such asfunctions_workflow_chat_delivery, bound to its test doubles insys.modules.test_workflow_loop_schema.py::test_group_iterable_scope_is_bound_to_the_server_normalization_context.sys.moduleschange, and two guard tests intest_workflow_draft_service.pyenforce that.admin_settings_fields.py,functions_personal_workflows.py,functions_settings.pyandroute_frontend_admin_settings.py) and auto-merges cleanly. If it lands first, I'll merge V2 and re-run the admin-settings,test_orchestration_actions_admin.py, cap-count and hand-off settings suites in 4 modes.Follow-ups
_require_approval_floor: "A plan that starts a saved workflow runs only when you run it yourself…". A hand-off-specific message means changing shared plan-revision code.Documentation
docs/explanation/release_notes.md, v0.261.238)docs/explanation/features/CHAT_ORCHESTRATION_WORKFLOW_HANDOFF.md,docs/admin/orchestration.mdanddocs/_data/features.yml, plus the regenerateddocs/_data/app_surface.ymlSecurity checklist
@swagger_route(security=get_auth_security())(all 4; the swagger-routes guardrail passes)sanitize_settings_for_user(). No new non-admin settings output; the two settings render only on the admin pane.Head SHA:
d2af803b1dfaa565e3f16feb16c00a13c1cd2192