Repository navigation
feat: add per-component routing and in-engine agentic conversion - #97
Open
matthewmoorcroft wants to merge 138 commits into
Open
matthewmoorcroft wants to merge 138 commits into
matthewmoorcroft wants to merge 138 commits into
Conversation
Group pipelines into connected components over control lineage and recommend a deterministic/agentic strategy per component (routing.py), recording the user's decision as an additive conversion plan (models/conversion_plan.py). Drive the in-engine agentic conversion flow (route_agentic.py) that routes, alters and fills the remaining gaps, with plan-bound gap combine/dedupe scoped to the routed pipeline's own tasks and mandatory bundle validation. Expose route and fill through the adapter CLI and MCP, and update AGENTS.md. Consolidates PRs #77 (per-connected-component routing agent), #92 and #93 (in-engine agentic conversion flow + CLI/MCP parity + cross-review fixes). Co-authored-by: Isaac <no-reply@databricks.com>
Propagate the flowx-enrich skill (from #95) up the stack ahead of adding the routing skill changes on this branch. Co-authored-by: Isaac <no-reply@databricks.com>
…ills Adds the first-class flowx-route skill (interactive route → decide → record plan → fill routed-agentic groups) and updates flowx-migrate to the discover → enrich → route → convert/fill → package flow. Registers flowx-route and the already-present flowx-resolve-airflow-gaps in the manifest, so it now lists both flowx-enrich (from the agentic-insights PR) and flowx-route. Convert runs as the deterministic baseline before routing; no plain convert re-run after routing (only the additive merge/combine fills), so routed-agentic placeholders survive to package. Customer decides the route per component; the agent presents options and authors the approved fill. Skill/manifest docs only; no src/ changes. Co-authored-by: Isaac <no-reply@databricks.com>
…rder 1. Airflow per-gap fill: convert --merge-agentic is ADF-only. Airflow's --merge-agentic is disabled and exits 2, so direct Airflow gaps to the flowx-resolve-airflow-gaps skill; removed the false --source airflow suggestion (route + migrate). 2. Merge behavior: describe it accurately — merge_agentic_results replaces the FIRST task whose name matches activity_name (matched by name, not verified to be a PlaceholderActivity). Removed the unfounded "replaces only placeholder tasks" guarantee (route + migrate). 3. Migrate sequencing: make the whole skill consistently linear — discover -> enrich -> convert (deterministic baseline) -> route (decide + edit) -> fill agentic gaps -> package. Fixed frontmatter, intro, and phase list; reordered and renumbered the workflow steps (convert now Step 5 before route Step 7, fill Step 8) so it reads top-to-bottom in execution order with no forward/back jumps; reordered the MCP command sequence to put convert before route. Grounded against adapter/__main__.py, sources/airflow/convert.py, and ir_serde.py. Skill-doc edits only; no src/ changes. make fmt + make test green. Co-authored-by: Isaac <no-reply@databricks.com>
merge_agentic_results / _find_and_replace_task (ir_serde.py) replaces the matched task with the authored task definition verbatim and does not set or mutate any `status` field. Reword the flowx-route merge note to describe the in-place replacement accurately and drop the false "status becomes translated" claim, consistent with the name-match description above it. Skill-doc edit only; no src/ changes. make fmt + make test green. Co-authored-by: Isaac <no-reply@databricks.com>
…e claim Airflow discovery does not yet emit control lineage or motifs (sources/airflow/discover.py), so it cannot form routing components, and convert --merge-agentic --source airflow is disabled (sources/airflow/convert.py). The route -> edit -> fill agentic-conversion flow is therefore ADF-only today; Airflow aligns via #63. - flowx-route: removed the false "Airflow (RunJob) emit control edges, source-neutral routing" claim; added an "ADF only (current scope)" note near the top directing Airflow agentic gaps to flowx-resolve-airflow-gaps. - flowx-migrate: added the ADF-only scope caveat to the Context intro and Step 7 (route); deterministic discover/convert/package still described as working for both sources. Skill-doc edits only; no src/ changes. make fmt + make test green. Co-authored-by: Isaac <no-reply@databricks.com>
…A-grounded channel FIX 2: apply_combine_fill now asserts every agent-authored pipeline carries tags.source == 'adf' (routing/agentic is ADF-only). A missing/wrong tag fails the combine closed (nothing written) with a clear message, instead of being accepted at combine and only rejected later at package preflight. The required tag is now documented in the flowx-route AgenticComponentActivity / combine authoring example. FIX 3: Make combine idempotent. The recorded plan still lists a component's members after a prior combine collapsed them out of the report, so the plan/membership check passes on a re-run; a naive second run then re-appends the authored pipeline (duplicate). Detect the already-combined state (members gone from the report, authored pipeline(s) already present) and no-op (already_combined=true). Regression test: running combine twice with the same members + authored pipelines yields a single authored pipeline. FIX 5: Persist a compact metadata/route_audit.json (routed components + decisions + gaps introduced) before package prunes .work/, so the "what did routing change?" trail survives the default prune. Additive and non-breaking: it writes nothing when no conversion_plan.json was recorded, so the no-route path stays byte-identical. .work/ pruning is unchanged. FIX 8b: In flowx-route's fill authoring guidance, default the Lakeflow pipeline/Connect resource channel to 'current' (stable/GA); only use 'preview' with a cited GA-status justification + confirmed workspace availability. Require the same GA-vs-Preview verification/citation as flowx-enrich (FIX 8a) before recommending any connector / Lakeflow Connect pattern. Co-authored-by: Isaac <no-reply@databricks.com>
…entic Bring the discover warning/units, enrich edge_type guidance, and GA-grounding into the tip so it carries all Phase-1 fixes (1, 7, 4, 8a, 2, 3, 5, 8b). Co-authored-by: Isaac <no-reply@databricks.com>
…name sets
Cross-review BLOCKING 1: the prior FIX 3 detection inferred "already combined"
from pipeline-name sets, which broke two ways:
(a) re-running combine with the SAME --members but a differently-named authored
replacement fell through to combine_group_fill (members already gone, so
nothing dropped) and appended a second authored pipeline -> duplicate;
(b) an authored pipeline whose name collided with a former member defeated the
detection entirely.
Fix: each successful combine now stamps an additive `_combine_provenance` marker
onto the report recording (component_id, inventory_sha256). Idempotency keys off
that recorded state -- never off comparing authored names to member names -- so a
re-run is detected regardless of how the authored replacement is named or whether
a name collides. The marker lives in the report, so a fresh convert (which
rewrites the report) clears it and combine re-applies after a genuine re-convert.
The marker is an extra top-level report key: ignored by the package phase (not a
recognized report shape key) and by ir_serde (which reads per-pipeline IR, not the
report wrapper), so the convert->package contract shape is unchanged.
Regression tests cover both failure modes: (a) a differently-named authored re-run
does not duplicate, and (b) an authored name colliding with a former member is
still detected as already-combined.
Co-authored-by: Isaac <no-reply@databricks.com>
…g-agentic Bring the --adf-source-path warning correction and the enrich GA-grounding rework into the tip, so it carries all cross-review re-fixes (FIX 1, 8a) alongside its own FIX 3 idempotency rework. Co-authored-by: Isaac <no-reply@databricks.com>
…nto stack/routing-agentic Co-authored-by: Isaac <no-reply@databricks.com>
…osure
Mirror the per-pattern release_state / release_state_source onto AgenticPattern
(they already ride into the agentic option via the {**pattern} spread; this makes
the parity explicit and typed). Add a neutral, per-pattern release_disclosures list
to AgenticOption: one entry per recommended pattern whose state is disclosed
(public_preview / private_preview / beta), carrying the pipeline, pattern, state, a
factual label, and a human-readable message. It is pure factual labelling, not a
warning:
- ga / unknown -> silent (no entry; unknown is treated exactly like ga);
- public_preview -> label "Public Preview (production-ready)";
- private_preview -> label "Private Preview";
- beta -> label "Beta".
The route skill points its channel guidance at the structured release_state and
reflects the neutral labels. The separate recommendation-eligibility rule (treat
Private Preview as doNotSuggest without confirmed enrollment) and the
verify-before-recommend grounding rule are unchanged.
Co-authored-by: Isaac <no-reply@databricks.com>
… stack/routing-agentic Co-authored-by: Isaac <no-reply@databricks.com>
…ack/routing-agentic Co-authored-by: Isaac <no-reply@databricks.com>
…lease_state examples) into stack/routing-agentic Co-authored-by: Isaac <no-reply@databricks.com>
The Module Descriptions table and the "Adding a New Deterministic Translator" guide still pointed at modules removed in the source-split refactor (parser/adf_loader.py, translator/engine.py, translator/activity_translators/). Repoint them at the current sources/<source>/ layout (sources/adf/loader.py, sources/adf/translate.py, sources/adf/translators/) and add the new modules this branch introduces (sources/adf/discovery_mapping.py, sources/airflow/, route_agentic.py, models/discovery.py). Co-authored-by: Isaac <no-reply@databricks.com>
CLAUDE.md previously re-imported AGENTS.md via the `@AGENTS.md` line. Replace it with a real relative symlink (git mode 120000) so AGENTS.md is the single canonical onboarding doc and the two never drift. Co-authored-by: Isaac <no-reply@databricks.com>
Record the bar for maintaining AGENTS.md now that it is the single canonical onboarding doc, so future edits keep it concise and pointer-first. Co-authored-by: Isaac <no-reply@databricks.com>
Agentic routing is ADF-only in Phase 1, but only fill-agentic combine checked the source: route with an agentic decision on an Airflow inventory recorded the plan and rewrote the DAG's deterministic tasks into placeholder gaps, outside the Airflow per-gap resolver. validate_plan now rejects an agentic decision unless the inventory source is adf (an inventory with no source predates the unified emitter and counts as ADF), and the recommendation for such an inventory is deterministic with a finding that says why. The route and migrate skills say this instead of promising Airflow routing once #63 lands. Route test fixtures now declare source adf. Co-authored-by: Isaac <no-reply@databricks.com>
Package now refuses to write a bundle when metadata/conversion_plan.json was recorded against a different inventory fingerprint than the current one, or when the report's combine provenance does not match an agentic component in the plan. route records the hash of the report it first edited under _route_provenance, and route_audit.json records the current inventory fingerprint plus the plan, baseline report and packaged report hashes. Co-authored-by: Isaac <no-reply@databricks.com>
…ntract Co-authored-by: Isaac <no-reply@databricks.com>
… graphs, flowx-owned passthrough fields) into stack/routing-agentic Co-authored-by: Isaac <no-reply@databricks.com>
The shared emitter now reads motifs from each graph's lineage, so the routing recommendation tests attach them with the ADF loader's helper. Co-authored-by: Isaac <no-reply@databricks.com>
conversion_plan.json was built and read as raw dicts; the dataclasses only backed tests. ConversionPlan now loads, round-trips and writes the plan and every reader goes through it: route records it, apply_plan applies it to the IR after convert, the combine fill and package check it, and the route audit reads it. Schema 2 binds the plan to what it was decided on: the inventory fingerprint, the saved source_graphs.json hash the inventory records, and the saved source_insights.json hash when enrich ran. Package refuses to run when any of them no longer match. Decisions stay per component; each component carries a reserved assignments list for per-node or subgraph routing (deterministic, agentic with a named pattern, or mixed at a boundary), which must stay empty for now. Co-authored-by: Isaac <no-reply@databricks.com>
This was referenced Oct 5, 2026
Member
Author
|
Thanks Greg. All ten are fixed, each with a test that failed first:
The branch also merges #96's round-three head (3406913, no conflicts). At bf07b01 the unit suite passes (2454) and ruff and mypy are clean. |
matthewmoorcroft
added a commit
that referenced
this pull request
Oct 21, 2026
Conflicts in bundler/dab_writer.py, resolved keeping both sides: - write_bundle_group keeps main's per-workflow loop and drops the old resources/*.yml clearing refresh 5 placed there; #97 now archives the resource files a package did not write, after writing. - main()'s packaging modes keep their own preflights; once a mode's preflight passes, #97's _archive_stale_bundles runs with that mode's real bundle folders (the output folder for single or shared Airflow, else one folder per group), before anything is written. - #97's option A code uses main's to_lowercase_key, and the top-level bundle's entries include main's pyproject.toml. Integration adaptations: discover's reset also moves main's DEPLOY.md and pyproject.toml (the test's managed list follows), and the package skill's option A text names packaging modes. Co-authored-by: Isaac <no-reply@databricks.com>
matthewmoorcroft
added a commit
that referenced
this pull request
Oct 21, 2026
…ndle MCP route returns the recorded conversion_plan.json and routing_review.html through the same size-bounded inline bundle as enrich prepare. A file over the 2 MB cap was listed under truncated and left out, but route still returned ok: true, so a hosted agent could be told to edit a plan it never received. Route now returns ok: false with an error naming each file left out and its size, and the upload paths to pass instead, the same rule enrich prepare follows for the inventory. Uploads are unchanged. The tool description and the route skill say so. Tests cover each file over the cap and an upload of an oversized plan. Co-authored-by: Isaac <no-reply@databricks.com>
Route stamps a routing record whenever it applies a stored gap fill, even when every component is deterministic. Package only checked the plan when the report had no record: an all-deterministic plan passed without reading agentic_conversion.json. So after a re-convert, package shipped the fresh report and silently left out the stored fill. Package now refuses a report with no record when the store holds a gap fill, and says to re-run route, which applies the fill or drops it when it was merged against an earlier convert. The route skill lists the refusal. A test re-converts after a merge on an all-deterministic plan, checks package refuses, then re-routes and checks the package carries the fill. Co-authored-by: Isaac <no-reply@databricks.com>
A successful route writes conversion_plan.json and routing_review.html in the same call, but MCP route delivered only the files it found and still returned ok: true when one was gone, for example after a discover in between. It now returns ok: false naming the missing file and saying to run route again. After a failed route it still returns whichever file already exists. The test double now writes both files the way the real command does. Found by the Codex cross-review of the inline-cap fix. Co-authored-by: Isaac <no-reply@databricks.com>
With no conversion plan the folder is not routed, so package ignores the stored agent outputs and gap fills; that is how a report whose plan went missing is packaged without routing after a re-convert. The route skill's new refusal line now says so, as the Codex cross-review pointed out. Co-authored-by: Isaac <no-reply@databricks.com>
…alse MCP route checked that the plan and review page existed and then copied them, so a discover moving one in between raised out of the command instead of returning ok: false. It now copies each file first and counts one that cannot be copied as missing, taking sizes from the copies. The docstring, the tool description and the route skill now say ok is false when either file is missing (run route again) or too large to return inline (pass an upload path). Both found by the Codex cross-review of the previous commit. Co-authored-by: Isaac <no-reply@databricks.com>
fill-agentic loaded agentic_conversion.json, then wrote the store, the report and its gaps with nothing held in between, so two fills on one output folder (an agent filling two components in one turn over MCP) both returned ok: true and the last write dropped the other unit. Route and a merge into the live report rewrite the same files the same way. The three now hold metadata/.route.lock while they work, a file created with O_EXCL holding a random token, the shape enrich's lock already uses, and refuse with a violation, writing nothing, while another holds it. The lock is removed when the command finishes or raises; only a killed run leaves it, and the message says how to clear it locally and on the hosted server. A merge into a separate copy takes no lock. The route skill and the MCP tool description say to run them one at a time per output folder. Tests hold the lock and check that fill, route and an in-place merge each refuse and write nothing, that a merge into a copy still runs, and that each command, including a fill that raises, leaves no lock behind. Co-authored-by: Isaac <no-reply@databricks.com>
Three gaps the Codex cross-review found in route's file delivery: - after a failed route, a file over the inline cap replaced the whole bundle with a size error, though a failed route should return whichever files exist as they are; it now does, with truncated listing the file; - a file the inline reader skipped because it is not UTF-8 text was reported as too large; it is now named as not text; - any copy error was reported as a missing file to fix by re-running route; only a missing file is, and other errors carry their reason. The too-large check now reads the bundle's truncated list. Tests cover each case. Co-authored-by: Isaac <no-reply@databricks.com>
load_agentic_output checked only components and gap_fills, and every save rebuilds metadata/agentic_conversion.json from those two keys, so any other top-level key was accepted and then dropped without a word on the next fill, merge or re-route. The loader now refuses a key outside those two, naming it, so route, fill-agentic, the merge and package all refuse that store and nothing is rewritten. The route skill's list of package refusals says so. A test adds a hand-written key and a lookalike key to a filled store and checks that the load and the next fill refuse, with the store left as it was. Co-authored-by: Isaac <no-reply@databricks.com>
Route iterated inventory["pipelines"] directly in four places in routing.py and two in route_agentic.py, so a hand-edited inventory whose pipelines is a number or null crashed route with a TypeError. Enrich already reads the same list through _inventory_pipelines, which treats a value that is not a list as listing no pipelines and keeps only object entries. Route now reads it through that same helper, so both phases give one answer. Tests cover components and the recommendation for int, dict, string and null values, and a rebuild over such an inventory, where the baseline check still refuses the mismatch. Co-authored-by: Isaac <no-reply@databricks.com>
After a successful route, MCP route uploaded both files before checking them, so with an upload path a plan that is not text went out with ok: true, and a missing page still let the new plan be uploaded beside an older page while the error came back. It now copies and reads each file as UTF-8 first, and after a successful route returns the error, with nothing uploaded, when either is missing, cannot be copied or is not text; only then does it deliver, refusing an inline bundle that left either out for size. A failed route still returns whichever files exist. Each cause is named from the error actually raised. The docstring, tool description and route skill list the unreadable case and say nothing is uploaded. Tests cover a missing page and a non-text plan on both upload paths. Found by the Codex cross-review of the previous commit. Co-authored-by: Isaac <no-reply@databricks.com>
Three gaps the Codex cross-review found in the route lock: - a merge from another file into an output folder's live .work/translation_report.json overwrote it without the lock, because only a merge in place took it; any merge written to a live report now holds that folder's lock; - an output folder with no metadata/ yet held nothing, so two merges there could both run; the lock now creates metadata/ and holds it, and only a folder that does not exist is left unheld; - the hosted recovery for a stale lock said discover, then route and fill, but discover also moves .work/, so convert has to run first; the message and the route skill now say enrich if used, convert, route and fill. Tests cover each. Co-authored-by: Isaac <no-reply@databricks.com>
The store loader read a missing components or gap_fills key as empty, so deleting gap_fills by hand silently dropped every stored fill on the next route, fill or merge. Every save writes both keys, so the loader now requires exactly those two, naming any unknown or missing key. The test now covers both kinds of key and checks that the load, the next fill, route and package each refuse with the store left as it was. Found by the Codex cross-review of the unknown-key fix. Co-authored-by: Isaac <no-reply@databricks.com>
The docstring said a failed MCP route returns whichever of the plan and the review page exist as they are, but the inline bundle carries only UTF-8 text. It now says so. Co-authored-by: Isaac <no-reply@databricks.com>
Reading a malformed inventory list as empty kept route from crashing, but the Codex cross-review showed it can also make a pipeline look fully deterministic: activities written as an object read as none, so the pipeline had no agentic activity left, and a motif's member_task_keys written as a string covered every activity named by one of its letters. Values of 1 for activities or motifs, or a non-object root, still crashed. The route CLI now checks the inventory's shape before anything else and refuses, writing nothing, when the root is not an object, pipelines is not a list of objects, a pipeline's activities or motifs is not a list of objects, or a motif's member_task_keys is not a list of names, saying to run discover again. The readers themselves read such values as none without crashing, and motif coverage takes only names. The route skill says so. Tests cover each shape through the route command and the readers. Co-authored-by: Isaac <no-reply@databricks.com>
Route iterated the insights' pipeline_insights, recommended_patterns and pipeline_relationships without checking they were lists, so an edited insights block whose hashes were recomputed crashed route with a TypeError. Enrich validates these lists when it records them, and they only feed the agentic patterns and suggested groupings, so a value that is not a list now reads as none: it can only take a suggestion away, never add a capability. Tests cover each list. Found by the Codex cross-review of the inventory-list fix. Co-authored-by: Isaac <no-reply@databricks.com>
From the Codex cross-reviews of the lock and store follow-ups: - the stale-lock recovery said to run route again, but a merge or fill that was refused or stopped is not re-applied by route alone; the message and the route skill now say to run route and then that command again, and on the hosted server every fill and merge after convert and route; - the MCP tool description said only an in-place merge holds the lock; it now says any merge written to the live report does; - the copy-to-live merge test now keeps the source copy in a separate folder, so it fails if the lock were taken on the source's folder; - a new test merges a real gap fill, deletes the store's gap_fills key and checks the next merge refuses with the store left as it was. Co-authored-by: Isaac <no-reply@databricks.com>
The final Codex cross-review found two gaps in the inventory shape check: - a pipeline with no activities key, or no name, passed, so route read it as fully deterministic with nothing to convert, or dropped it; discover always writes both (motifs only when it found some), so route now refuses a pipeline without a non-empty name or an activities list; - the library's record_plan, which the route command calls, did not run the check, so another caller could record a plan over a malformed inventory; it now refuses the same shapes, writing nothing. The route skill lists the new cases. Tests cover a missing activities key, an empty name, and record_plan. Co-authored-by: Isaac <no-reply@databricks.com>
From the Codex cross-review of the inventory name and activities check: - a motif with no member_task_keys passed the shape check and read as covering nothing, though discover always writes the key; it is now refused like a member_task_keys that is not a list of names; - record_plan checked the inventory before its own arguments, so a call with no plan over a malformed inventory returned a shape failure instead of the documented ValueError; it now reads the plan argument first. Tests cover both. Co-authored-by: Isaac <no-reply@databricks.com>
From the Codex cross-review of the previous commit: - a motif with no string motif_id still counted as covering its members, and a numeric id was turned into text; discover always writes a string id, so the shape check now refuses any other, and the reader takes only string ids; - record_plan read the inventory before the plan argument, so with no plan source and an inventory that is not an object it raised the inventory error, not the documented one; it now reads the plan argument before any file. Tests cover a numeric motif id and both record_plan cases. Co-authored-by: Isaac <no-reply@databricks.com>
Brings #96's three fixes (authored component tasks declare and check the variables and files they name, a reloaded component field of the wrong shape is refused, parameter approximations reach SETUP.md), #95's enrich lock fix and #94's round-four fix up into #97, on top of #97's own fixes. Co-authored-by: Isaac <no-reply@databricks.com>
The merge brought up #95's enrich lock fix: when writing the token fails, enrich removes its lock only while the path is still the file it opened, so a lock a later enrich created after a discover survives. The route lock copied the earlier pattern and removed the file blindly after a short write, so it could delete a later route, fill or merge's lock and let a third command in, and an exception from the write left an empty lock behind. The route lock now follows the same rule, reusing enrich's file-identity and token checks. Tests cover a failed write removing only its own lock, and a failed write or close keeping a later command's lock. Found by the Codex cross-review of the merge. Co-authored-by: Isaac <no-reply@databricks.com>
flowx no longer keeps skills/flowx-route/platform-limits.json, a dated copy of the Databricks limits that would need maintaining as they change. Its shape test goes with it. Enrich now checks every pattern it recommends against the current Databricks platform limits, read live from the docs. It doesn't recommend a pattern a limit rules out, records each limit it relied on in the conversion_notes of every pipeline the recommendation covers, as "limit: <name> = <value> (source: <page>, read <date>)", marks a pattern "not qualified" when a fact or the docs are missing, and never uses a remembered number. Release state keeps its own rule. Route keeps checks C1 to C13, the arithmetic and the suggestion order, and reads the limits the same way. Each check now names its limits in words. The three claims the public docs don't state (one pipeline per destination table, a running gateway holding a concurrent update, and integrated CDC's release state) are marked as assumptions. The gateway count G and the For each concurrency F are read live and used in the checks instead of being fixed. A new test holds the skill text to this: no copy of the limits anywhere in the repository, enrich's rule and record form, route's live reading, each check's limits in words, and the three assumptions marked. The changelog line says the limits are read live. Co-authored-by: Isaac <no-reply@databricks.com>
This branch has not been deployed
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.
Intent
Route each connected component of an ADF factory to a deterministic (1:1 engine) or an agentic (agent-authored re-architecture) conversion. The user decides, and the record of the decision is one readable file. Convert stays deterministic and untouched.
metadata/conversion_plan.json, with every decision pending, and draws it as a standard review page.fill-agentic.metadata/route_audit.json.The plan can be edited and re-applied as often as needed. The same plan gives the same bytes, and only what changed is re-derived. Agentic routing is ADF-only.
This builds in the conversion-approach decisions of 8 Oct:
metadata/agentic_conversion.json, renamed fromagentic_combines.json.It also builds in:
route_audit.jsonhashes).What changed
Files feat: add per-component routing and in-engine agentic conversion #97 reads and writes:
source_graphs.json,agentic_insights.json(optional),inventory.jsonconversion_plan.json,routing_review.html.work/translation_report.json,.work/gaps.json,agentic_conversion.json,.work/route_baseline/(internal)route_audit.jsonThere is no separate recommendation or plan file.
models/conversion_plan.py, the typedConversionPlan(schema 3):null(pending);suggested_groupings, each withaccepted;conversationof{question, answer};agentic_insights_sha256(feat: add enrich step that records agent-authored insights on the discover inventory #95's rename);ValueError;assignmentsstay, as the Phase 2 extension point.routing.py:inferredrelationship or by a simplification pattern recommended in more than one component.dropped_groupings.sourceis not agentic-routable.insights_file_violationsrefuses whenagentic_insights.jsonand the inventory'sinsightsblock disagree, or the file fails its own hash. The message says how to recover.route_agentic.py:matched_activity_names) and for activities the inventory lists for its pipelines but the report no longer has as tasks. A name a pipeline that stays deterministic also holds is kept.fill-agentic:fill-agentic combineand stores outputs inagentic_conversion.json.replacedhistory, where the first fill is not a replacement.ir_serde.merge_agentic_results: after routing, an in-place merge of convert's own gaps is stored as a gap fill, and the baseline is never rewritten. The routing record'sgap_fills_sha256lets package spot a merge made aftermodify.bundler/dab_writer.py:route_audit.json: carries one canonical plan hash, per-unit outcomes andreplacedhistory, the gap fills and the routing conversation.reporting/routing_review.py+routing_review_template.html: one eight-section page in the Databricks style, written by route and fill-agentic:It uses the stdlib only, escapes all text, carries no timestamps, and has no logo. It is the only HTML page flowx generates.
adapter,mcp/server.py: route andfill_agenticfollow the flow above. MCP route returns the plan and the review page because a hosted agent can't readoutput_dir.Hosted MCP flow (13 Oct):
flowx-migrateskill told Genie Code to use one MCPmigratecall, which runs only discover, convert and package, so hosted runs never enriched or routed.flowxcall per phase. It passes the same source to discover and convert, because convert reads the source again.migrateis described as the deterministic-only shortcut in the skill, the server instructions, the tool description,app/README.mdand the guide. Its behaviour is unchanged.Inputs and SQL Server ingestion (14 Oct):
catalog.schema(defaultmain.default), never a "Custom" choice that can't take text. Theinputs packagedescriptions carry that format.resources/*.ymlbefore writing, so a unit switched back to deterministic no longer leaves its agent-built pipeline behind to deploy. Hand-added files inresources/are cleared too.Questions at scale (14 Oct):
conversion_notes, with the source system and connector a pipeline reads from. The ADF and Airflow source guides say where each fact shows.conversion_notes. Route reads the limits its checks rest on the same way, names each in words, and marks three claims the public docs don't state as assumptions: one pipeline per destination table, integrated CDC's release state, and whether a running gateway holds a concurrent-update slot. This replaced a storedplatform-limits.jsonon 28 Oct..work/and to re-run convert, then route. The route, package and migrate skills describe how to change the conversion after a package, and what survives each step.Answers on the open calls (14 Oct):
metadata/archive/<UTC time>/with anarchive.jsonof what moved, from where, when and each file's hash, and says so in its result. The hosted delivery puts the current bundle before the archive.accepted: a value that is not a JSON boolean ("true",1,null) is refused when the plan loads, and package refuses such a plan.tasksis not a non-empty list of task objects, or that is markedmigration_status: "excluded"; the insights check also refusesagentic_insights.jsonwhose recordedinventory_sha256no longer matches the inventory, in route and package.sources/adf/translate.py:.work/gaps.json, so the baseline is always convert's own output. Nothing in translation changes.Docs:
flowx-routeandflowx-migrateskills, convert notes, README and guide file trees, and the AGENTS.md routing rows. Docs about refactor: add source-neutral discovery graph contract and shared lineage core #84/feat(adf): save source graphs with dataset lineage at ADF discover #94/feat: add enrich step that records agent-authored insights on the discover inventory #95 internals were left to those PRs, and the Airflow gap-resolver skill registration to Peter.Version 0.2.0, with a CHANGELOG entry.
Fixes from checking this PR against the points raised in review (26 Oct), each with a test that fails without it and a Codex cross-review:
routereturnsok: false, uploading nothing, when the plan or the review page is missing, unreadable or left out of the size-bounded inline bundle;agentic_conversion.jsonstores a gap fill, as after a re-convert;metadata/.route.lock, built like enrich's lock, and refuse while another holds it;agentic_conversion.jsonmust hold exactlycomponentsandgap_fills;Follow-ups, not in this PR: MCP
packageandmigratealso return ok with a truncated inline bundle, whose cap counts characters (main's code); discover, convert and package take no route lock, and route, fill and package don't hold enrich's; a configured or--reportcopy is matched by its routing record only; duplicate JSON names keep the last value; the routing record's own key set; the plan's authored defaults; route recording the plan before an apply that fails;route_audit.jsonrewritten on each package; duplicate inventory pipeline names; a fill stored before this carry isn't re-checked on replay; a component fill refused by the bundle writer reports an error rather than violations.Carries feat: add AgenticComponentActivity for agent-authored bundle components #96's head
cb7f0d2(with feat: add enrich step that records agent-authored insights on the discover inventory #95's lock fix and feat(adf): save source graphs with dataset lineage at ADF discover #94's round-four fix) through a no-conflict merge,5edec1c.uv.lockis identical to feat: add AgenticComponentActivity for agent-authored bundle components #96's.How it was tested
Unit tests: about 4,700 lines across 13 routing, plan, review-page, insights-check, MCP and package-preflight test files, with a regression test for each change. The full suite (1816 passed), ruff and mypy pass, and CI is green.
Gap ownership (13 Oct): three regression tests in
test_route_agentic_flow.py, each red before the fix:The no-mistakes test step also drove the real CLI on a hand-written ADF factory, 5 of 5 scenarios. They include package refusing the unfilled unit and switching back to deterministic, which restores
gaps.jsonbyte for byte. Deterministic-only output is still byte-identical to feat: add AgenticComponentActivity for agent-authored bundle components #96's head07eae42.Questions at scale (14 Oct): a test that route and fill-agentic after a package give the pruned-
.work/message, red before the change. A test holds the skills to reading the limits live: no copy of them in the repository, enrich's rule and record form, route's live reading, each check's limits named in words, and the three assumptions marked (red before the 28 Oct change). The no-mistakes review then added the enrich database-source flag, aligned the suggestion order with the checks, counted gateways in gateway mode, and reverted a fix round's rewrite ofuv.lockto an internal package mirror.Answers on the open calls (14 Oct): discover over a routed, filled and packaged folder archives every file it replaces with its bytes intact, and a fresh folder archives nothing (red on the old code); hosted migrate reports the archive and delivers the current bundle first; non-boolean
acceptedis refused at load and by package; a list or object pipeline name is refused by fill-agentic (each red before its fix). A fill whosetasksis a string, an object, null or a list of strings, or that is marked excluded, is refused and stores nothing, and a hand-edited deterministic inventory field is refused by route and package (red before; a missing or emptytaskslist was already refused as an empty job).Hosted MCP flow (13 Oct): the server's commands were driven on the ADF fixture factory as Genie would, with the definitions inline: discover, enrich (prepare and apply), convert with the same definitions, route pending, route decided, then package, which wrote
route_audit.json. Convert without the source fails with--source-dir is required, which is what the skill's earlier advice missed.Deterministic output: ADF and Airflow fixtures run through discover, convert and package give output trees (bundle, metadata,
.work) byte-identical to feat: add AgenticComponentActivity for agent-authored bundle components #96's head07eae42(366 ADF files, 130 Airflow files).End to end, on the full ADF fixture factory:
fill-agentic.agentic-applied, the gap fill and the conversation.Codex cross-review, read-only:
9221edc, each with a test:11785e9): two P2 findings, both fixed red-green:517af7c;9b8b9ae.No-mistakes review rounds: then fixed, among others:
modifygoing unnoticed;gaps.jsonafter a re-convert;Untilchildren.Deferred follow-ups:
metadata/lists;dropped_groupingsshowing only on the run that drops the grouping;Merge order
Merge #84 first, then #94 retargeted to
main, then #95, then #96, then this PR (retargeted tomainonce #96 has merged).This pull request and its description were written by Isaac.