Repository navigation
docs(orchestration): deprecate WorkflowPlanner in place with a test that holds the invariant (#13751) - #13760
Merged
Merged
Conversation
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
…supersedes each capability (#13751) Every public method on orchestration.WorkflowPlanner has zero callers. It is the planner half of the engine orchestrator.run_workflow stopped using at #5058; #12373/#12579 deprecated the executor half in place and did not reach this sibling. Orchestrator still constructs it as self._step_planner but never calls a method on it. Unlike the executor, it holds no capability the canonical path lacks, so nothing here is staged for consolidation. Each capability maps to wired code: plan_workflow_steps_with_agents -> create_workflow_plan plus AgentRouter.get_agent_recommendations_scored; trajectory priors -> _fetch_planning_context (#10580/#10581, tenant-scoped #11015/#11089); get_plan_summary -> create_workflow_plan, which plans without executing; create_plan_summary_for_approval -> the wired services WorkflowExecutor .present_plan_for_approval (#390). Wiring it in was the alternative and is the wrong move: it would recreate a second planning path for the two to drift against, which is what #13751 was filed to avoid. Also corrects two stale claims. workflow_planning.py called WorkflowPlanner "the canonical (but currently unwired)" planner, inverting the relationship — StrategyPlanner there is the wired one. And the orchestrator comment implied _step_planner was a live collaborator. All code retained; nothing deleted. Prior tracking was #6820, which closed 2026-05-17 having wired three of its four orphaned modules and left this one.
…13751) #12579 recorded the sibling executor's deprecation in docstrings only, with nothing holding the claim, so it could go stale in either direction — wired while still documented dead, or the note dropped while still true. That is partly why the planner half went unnoticed through #6820 and #12579. Five guards, each verified by deliberate failure: - no attribute access through orchestrator._step_planner (AST, so the construction assignment is distinguished from a call) - the two uniquely-named deprecated methods appear nowhere in production - an allow-list of the three files permitted to mention the class, each with its reason, failing in both directions - the module and package docstrings still carry the notice AND name the canonical replacements - the class and all five public methods still exist — deprecation must not become quiet deletion Deliberately does not scan for the bare name get_plan_summary: OverseerAgent defines an unrelated wired method with the same name, so a name-only scan would false-positive on api/overseer_handlers.py.
4 tasks
4 tasks
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.
Closes #13751
Thinking Path
#13751 offered two branches: wire the two unreachable methods into a request path, or
record why the capability is superseded, naming the superseding code. Investigation settled
it decisively toward the second — and turned up more than the issue described.
The scope is bigger than two methods.
create_plan_summary_for_approvalalso has zerocallers, so every public method on
WorkflowPlanneris unreachable. Only__init__runs,via
orchestrator._step_planner, which is never called on.There is a precedent, and this is its missed sibling.
run_workflow's docstring recordsthat #5058 removed the
orchestration.WorkflowPlanner/WorkflowExecutorpath. #12373/#12579then "declare[d] services WorkflowExecutor canonical, deprecate[d] dead orchestration engine
in place" — the executor half. It never reached the planner half. This PR applies the same
convention to the sibling it missed.
Wiring would be the wrong move. Every capability already exists in wired code:
WorkflowPlannercapabilityorchestrator.create_workflow_plan+AgentRouter.get_agent_recommendations_scored(viaWorkflowRunner)orchestrator._fetch_planning_context(#10580/#10581), tenant-scoped by #11015/#11089orchestrator.create_workflow_plan— builds and stores aWorkflowPlan, does not run itservices.workflow_automation.executor.WorkflowExecutor.present_plan_for_approval, viaWorkflowAutomationManager(#390)Unlike the executor — which #12579 kept for genuinely unique DAG/checkpoint/sub-workflow
capabilities under #12577 — this module has nothing the canonical path lacks. Wiring it
would recreate a second planning path for the two to drift against, which is precisely what
#13751 was filed to prevent.
This module's status has now surfaced three times. #6820 tracked it as one of four
orphaned modules and closed 2026-05-17 having wired three and left this one. #12579 deprecated
its sibling and skipped it. Both prior passes recorded intent in prose that nothing held —
which is the actual root cause of the recurrence, and why this PR adds a test.
What Changed
orchestration/workflow_planner.py— module and class docstrings record the deprecationwith the per-capability supersession table above, and state explicitly that wiring it in is
not the intended fix. All code retained.
orchestration/__init__.py— package docstring marksworkflow_plannerdeprecated,mirroring the existing
workflow_executornote. Re-export kept, exactly as #12579 kept theexecutor's.
orchestration/workflow_planning.py— corrects a stale claim that inverted reality:it called
WorkflowPlanner"the canonical (but currently unwired)" planner. It is notcanonical;
StrategyPlannerin that very module is the wired one(
orchestrator._strategy_planner, thecreate_workflow_planfallback).orchestrator.py— the comment on_step_plannerimplied a live collaborator. Nowrecords that no method is ever invoked, and why the attribute is retained anyway.
repo_tests/workflow_planner_deprecation_test.py— new; the guard #12579 lacked.Verification
Each guard proven to catch its own regression — five deliberate breakages, each failing
in its own distinct test rather than one blanket assertion:
self._step_planner.get_plan_summary(...)test_planner_instance_is_constructed_but_never_calledplan_workflow_steps_with_agentsin productiontest_unique_planner_methods_have_no_production_call_sitesWorkflowPlannertest_only_known_files_reference_the_plannerDEPRECATEDnoticetest_module_and_package_record_the_deprecationestimate_step_durationtest_deprecated_code_is_retained_in_fullThat last guard matters: it makes "deprecate in place" enforceable, so this can never quietly
become a deletion.
One trap worth naming: the guard deliberately does not scan for the bare name
get_plan_summary.OverseerAgentdefines an unrelated method with that name which iswired via
api/overseer_handlers.py, so a name-only scan false-positives. Reachability ispinned through the instance attribute (AST, distinguishing the assignment from a call) and
the import allow-list instead.
Regression sweep:
Local Python is 3.10 with backend deps partially absent — CI is the authority.
Decision
Taking the "record superseded" branch of #13751's own acceptance criteria rather than the
"wire it in" branch. Reversible, and flagged rather than buried: if you would rather these
capabilities be wired and the canonical path retired instead, say so and I will invert it —
but that is a larger architecture change than #13751 scoped, and it would need #12577's
consolidation to lead.
AC 3 needs reinterpreting, and I want that visible rather than silently ticked. It asks
for "a test [that] exercises each through its production entry point". Under the supersession
finding there is no production entry point, so that AC is unsatisfiable as written. The
guard test above is what replaces it: it pins the absence of a production path, so the
finding cannot rot. #13730 already added tests that drive both methods directly, so the code
itself is covered.
Model Used
Claude Opus 5 (1M context)