Phase D: Retire DefinitionBasedOrchestrator + Journey/Report Views
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Close out the north-star. Retire the legacy node/edge graph engine (DefinitionBasedOrchestrator, the nodes/ package, the PipelineDefinition.config graph, the definition HTTP endpoints and CLI flags) now that routing (Phase A) + flag-driven stages (Phase B) + durable ingest (Phase C) fully replace it. Add the journey projection (admin panel + manage.py trace) and a manage.py report read model over Node/Pipeline/Incident.
Architecture (decisions locked in brainstorming — do NOT relitigate):
- Full delete + drop the
configcolumn.PipelineDefinitionstays — it is the routing model (match/priority/run_*/channelsfrom Phase A). What’s retired is its legacyconfignode/edge graph and everything that reads it. A destructive migration dropsconfig+version-on-config logic;get_nodes/get_defaults/get_entry_nodeare removed. - Journey = projection, no new model. An admin panel on
Alert/Incidentandmanage.py trace <alert-id | trace_id>render the existing chainAlert → Incident → PipelineRun → StageExecution+ the matchedPipeline. - Report = a CLI command, no endpoint.
manage.py report [--json]aggregates overNode/Pipeline/Incident+ inbox depth (per-node incident counts, per-pipeline routing hits, recent activity). Consistent withdoctor/monitor_pipeline. No new HTTP surface (an API can come later if a concrete consumer appears).
What is verified unused (safe to retire): the 8 nodes run run_pipeline --checks-only --json via cron — the main PipelineOrchestrator path, never --definition/--config. The definition endpoints and graph CLI have no production consumer.
Retirement inventory (delete unless noted):
apps/orchestration/definition_orchestrator.py(the engine)apps/orchestration/nodes/(whole package:base/context/ingest/intelligence/notify/transform)apps/orchestration/services.py—PipelineInspector+PipelineDetail(graph inspection); keep any non-graph helpersapps/orchestration/management/commands/show_pipeline.py(“Display pipeline definitions” — graph) andpipelines/*.jsonsamplesrun_pipeline— remove--definition/--config/--payloadbranches + theDefinitionBasedOrchestratorimport; keep the command and--sample/--checks-only/--dry-runviews.py—PipelineDefinitionListView,PipelineDefinitionDetailView,PipelineDefinitionValidateView,PipelineDefinitionExecuteViewandPipelineListView(pipelines/, lists graph definitions viaget_nodes); keepPipelineView(pipeline/,pipeline/sync/),PipelineStatusView,PipelineResumeViewurls.py— thedefinitions/*routes +pipelines/; keep the run routesPipelineDefinition.configfield +get_nodes/get_defaults/get_entry_node; adminnode_count; theconfig-change branch inPipelineDefinitionAdmin.save_model- All associated tests (
test_definition_orchestrator.py,nodes/tests, definition tests intest_views.py/test_services.py,show_pipelinetests,run_pipelinedefinition tests)
Design: docs/plans/2026-08-01-pipeline-routing-north-star-design.md (“retire DefinitionBasedOrchestrator; journey; report read model”). Builds on A (#183), B (#184), C (#185) — all merged.
Tech Stack: Django 5.2, pytest, uv. Conventions: absolute imports; line length 100; 100% branch coverage on changed lines; TDD; one commit per task; never push to main (feature branch + PR).
Task 0: Branch setup
git checkout main && git pull
git checkout -b feat/pipeline-routing-phase-d
Expected: fresh branch off main with A+B+C present (routing fields, _downstream_stages, process_inbox, no Celery).
Task 1: Retire the definition HTTP endpoints + graph pipeline list
Why: Remove the legacy definition API first (top of the dependency chain), so later deletions don’t break URL routing/tests.
Files: Modify apps/orchestration/views.py, apps/orchestration/urls.py; update apps/orchestration/_tests/test_views.py.
Steps (TDD-in-reverse for deletion):
- Delete the four
PipelineDefinition*views +PipelineListViewfromviews.pyand their imports (DefinitionBasedOrchestrator,PipelineInspector,get_nodesusages). - Delete the
definitions/*andpipelines/routes fromurls.py. - Delete the corresponding tests in
test_views.py(definition list/detail/validate/execute, pipeline-list). Keep the trigger/status/resume/sync tests (incl. the Phase Ctest_async_mode_records_pending_run). - Run
uv run pytest apps/orchestration/_tests/test_views.py -q→ green; confirm no other test references the removed routes (grep -rn "definitions/\|pipeline-list\|PipelineDefinitionListView").
Commit: refactor(orchestration): remove legacy definition HTTP endpoints.
Task 2: Trim run_pipeline to the main orchestrator; drop graph CLI + samples
Files: Modify apps/orchestration/management/commands/run_pipeline.py; delete apps/orchestration/management/commands/pipelines/ (JSON samples) and show_pipeline.py; update/trim their tests.
Steps:
- In
run_pipeline.py: remove--definition,--config,--payloadarguments, theDefinitionBasedOrchestratorimport, and the branch that runs a definition. Keep--sample,--checks-only,--source,--dry-run, and the hardcodedPipelineOrchestratorpath. Update the module docstring/examples. - Delete
show_pipeline.py(+ its tests) and thepipelines/*.jsonsample configs. - Update
run_pipelinetests: drop definition/config cases; keep sample/checks-only/dry-run. grep -rn "show_pipeline\|--definition\|--config\|pipelines/.*\.json"acrossapps/,bin/,docs/to catch stragglers (e.g.bin/aliases, docs command tables) — fix or note.- Run the checkers/orchestration suites.
Commit: refactor(orchestration): run_pipeline drops --definition/--config; remove graph samples.
Note (deviation from draft): keep
--payload/--file— they feed the main orchestrator, not the graph. Only--definition/--configand the three definition-only methods (_get_definition,_show_definition_dry_run,_display_definition_result) are removed.
Task 2b: CLI setup consolidation — retire setup_instance, extract setup_intelligence, de-dup the cluster menu
Why (decided with the user 2026-08-04): setup_instance is the graph-era standalone wizard — it builds PipelineDefinition.config (the retired node graph) and renders via PipelineInspector, so it blocks Task 4. It also overlaps setup_cluster (two wizards) and the cluster menu carries two setup entries for what one command does. Agile: retire the leftover, one clear door per concern. Its one unique-and-still-useful step (AI provider setup) is unrelated to cluster → it moves to its own command in the intelligence app, not folded into setup_cluster.
Steps:
- Extract
setup_intelligence. Createapps/intelligence/management/commands/setup_intelligence.py(+ tests): prompt for a provider fromapps.intelligence.providers.PROVIDERS; if notlocal, collectapi_key+model(default fromprovider_cls.default_model); upsert the single activeIntelligenceProvider(respecting its single-active invariant).--provider/--api-key/--modelfor non-interactive. Surface it inbin/cli/intelligence.shas “Configure AI provider”. - Retire
setup_instance.git rm apps/orchestration/management/commands/setup_instance.py+apps/orchestration/_tests/test_setup_instance.py; remove thesm-setup-instancealias (bin/install/aliases.sh,bin/aliases.sh,bin/README.md), the install-menu “Set up monitoring instance (interactive wizard)” entry (bin/install.sh,bin/cli/install_menu.sh), and Installation.md’s “Pipeline workflow with aliases” section.grep -rn "setup_instance\|sm-setup-instance"→ only historicaldocs/plans/remain. - De-dup the cluster menu. In
bin/cli/cluster.shcollapse options 1+2 (“Set up as hub/agent (guided)”) into one “Set up this node (guided)” →manage.py setup_cluster(no--role; the command already prompts hub/agent). Keep the push options. Updatebin/tests/test_cluster_menu.bats. - Run pytest (intelligence + orchestration) + bats.
Commit(s): feat(intelligence): setup_intelligence command, refactor(cli): retire setup_instance; single guided setup entry.
After 2b, nothing writes
PipelineDefinition.config→ Task 4 (drop the column) is unblocked.setup_clusterhandles the channel; checkers run by default; AI provider issetup_intelligence— no capability lost, one door per concern.
Task 3: Delete DefinitionBasedOrchestrator, the nodes/ package, and the graph inspector
Files: Delete apps/orchestration/definition_orchestrator.py, apps/orchestration/nodes/ (whole dir), and the PipelineInspector/PipelineDetail classes in apps/orchestration/services.py (keep any non-graph code there); delete apps/orchestration/_tests/test_definition_orchestrator.py and any nodes//inspector tests.
Steps:
git rmthe engine, thenodes/package, and their tests.- Remove
PipelineInspector/PipelineDetailfromservices.py(and re-check the file still imports cleanly / has remaining used code). grep -rn "DefinitionBasedOrchestrator\|orchestration.nodes\|PipelineInspector\|PipelineDetail"across the repo → zero non-deleted references.uv run python manage.py check+ orchestration suite green.
Commit: refactor(orchestration): delete DefinitionBasedOrchestrator + node graph.
Task 4: Drop PipelineDefinition.config (+ graph methods, admin, migration)
Why: The graph column and its accessors are now dead. PipelineDefinition keeps only the routing fields.
Files: Modify apps/orchestration/models.py, apps/orchestration/admin.py; create apps/orchestration/migrations/00XX_drop_pipelinedefinition_config.py.
Steps:
- Remove
configfield andget_nodes/get_defaults/get_entry_nodefromPipelineDefinition. - In
admin.py: removenode_countdisplay + the “Configuration (legacy graph)” fieldset; simplifysave_model(drop theconfig-change/versionbump branch — keepcreated_bydefault). Adjustlist_displayif it referencednode_count. uv run python manage.py makemigrations orchestration→ a migration that removes theconfigcolumn.- Fix any test that constructed
PipelineDefinition(config=...)(several create it withconfig={}— remove that kwarg). - Full orchestration + admin + checkers-preflight suites green (preflight
check_pipeline_statereadsPipelineDefinition— confirm it doesn’t touchconfig).
Commit: refactor(orchestration): drop legacy PipelineDefinition.config graph.
After Tasks 1–4,
grep -rn "\.config\b" apps/orchestrationshould only matchNotificationChannel.config/ provider config — neverPipelineDefinition.config.
Task 5: Journey admin panel on Alert / Incident
Why: One-click lifecycle: given an alert/incident, show its run + stages + the matched pipeline.
Files: Modify apps/alerts/admin.py (Alert + Incident admin); test apps/alerts/_tests/ (admin).
Steps:
- Write the failing test: load the
Incidentchange page as admin; assert the response contains the run’strace_id, eachStageExecutionstage/status, and the matchedpipelinename (when set). Do the same shortcut onAlert(itstrace_idlinks to the run). - Implement a read-only “Journey” section — a
readonly_fieldsmethod (e.g.journey_display(self, obj)) returning safe HTML that walksIncident → pipeline_runs → stage_executions(ordered), showing stage, status, duration, output ref, and the matchedPipeline(incident.pipeline) with “why it routed” (the stamp). ForAlert, link viaalert.trace_id/alert.incident. Useselect_related/prefetch_relatedto avoid N+1; escape all values. - Handle the unhandled case: an alert with no run (inbox) shows “not processed — inbox” rather than an empty panel.
- Run alerts admin suite; 100% branch coverage on the new method.
Commit: feat(alerts): journey panel on Alert/Incident admin.
Task 6: manage.py trace <alert-id | trace_id> CLI
Why: The CLI shortcut for the same journey — for terminals/CI.
Files: Create apps/orchestration/management/commands/trace.py; test apps/orchestration/_tests/commands/test_trace.py.
Steps:
- Failing tests: given an alert id → prints its
trace_id, incident, thePipelineRunstatus, each stage (stage/status), and “handled by pipeline #N (priority P)” or “inbox — not processed”. Given atrace_iddirectly → same chain. Unknown id/trace →CommandError.--jsonemits the structured chain. - Implement: resolve the arg as an int alert id first, else treat as a
trace_id; walkAlert → Incident → PipelineRun(trace_id) → StageExecution[]; render text +--json. Reuse the routing stamp (incident.pipeline) for the “handled by” line. No new model — pure query. - 100% branch coverage.
Commit: feat(orchestration): manage.py trace <alert|trace_id> journey CLI.
Task 7: manage.py report read model (Node / Pipeline / Incident / inbox)
Why: The aggregate read model the north-star named — operational reporting without a new endpoint.
Files: Create apps/orchestration/management/commands/report.py; test apps/orchestration/_tests/commands/test_report.py.
Steps:
- Failing tests: with a couple of
Nodes,Incidents (some linked to nodes viaAlert.node, some open), andPipelineDefinitions (some stamped on incidents) →report --jsonreturns:nodes: per-node{instance_id, incidents, open}counts (viaAlert.node→Incident),pipelines: per-pipeline{name, routed}(count ofIncident.pipeline == p),incidents: totals by status,inbox:{pending, processing}(reuse thePipelineStatuscounts, same asdoctor). Text mode prints the aligned summary from the design preview.
- Implement with aggregate queries (
.values().annotate(Count)), no per-row loops. Reusedoctor’s inbox counts (extract a shared helper or duplicate the two counts — keep it simple). - 100% branch coverage.
Commit: feat(orchestration): manage.py report — node/pipeline/incident read model.
Task 8: Verify + docs + finish branch
Files: docs/Architecture.md (remove the “Definition-Based Pipeline” section + the dual-engine comparison table; the flat Pipeline is the only model now), docs/Index.md (drop definition endpoints/show_pipeline), apps/orchestration/AGENTS.md (remove node-graph/validate_config/_NODE_HANDLERS invariants; add journey/trace/report), any bin//README command tables referencing the removed CLI.
Steps:
- Full gate:
uv run black . --check && uv run ruff check . && uv run mypy . uv run python manage.py makemigrations --check --dry-run uv run pytest && uv run coverage run --branch -m pytest && uv run coverage report uv run pip-audit --strict --desc ./bin/tests/test_helper/bats-core/bin/bats bin/tests/Expected: all clean; 100% branch coverage on changed lines; no pending migrations.
- Docs updated;
grep -rn "DefinitionBasedOrchestrator\|definition-based\|show_pipeline\|--definition"acrossdocs/,bin/,README.md→ only historicaldocs/plans/remain. - Finish (superpowers:finishing-a-development-branch) — push, open PR to
main.
Acceptance criteria (“done”)
DefinitionBasedOrchestrator, thenodes/package, the graph inspector, the definition HTTP endpoints,show_pipeline, therun_pipeline --definition/--configflags, and the sample JSON pipelines are gone;run_pipeline --sample/--checks-only/--dry-runand the main orchestrator path are unchanged.PipelineDefinition.config(+get_nodes/get_defaults/get_entry_node) is dropped via migration; the model keeps only routing fields; nothing readsPipelineDefinition.config.- The
Alert/Incidentadmin shows a read-only Journey panel (run + stages + matched pipeline; “inbox — not processed” when unhandled). manage.py trace <alert-id | trace_id>renders the same chain (text +--json); unknown input errors cleanly.manage.py report [--json]reports per-node incident counts, per-pipeline routing hits, incident totals, and inbox depth.- All CI gates green: black, ruff, mypy,
makemigrations --check, pytest, 100% branch coverage on changed lines,pip-audit --strict, bats.
Out of scope (explicit)
- A report HTTP API — deferred until a concrete consumer exists (the CLI is the read model for now).
- Any change to the routing semantics, durable ingest, or drain (A/B/C are done).
- Re-homing sample pipelines elsewhere — the hardcoded
--samplepayload remains the demo path. - Touching
docs/plans/historical records.