Repository navigation
feat: add AgenticComponentActivity for agent-authored bundle components - #96
matthewmoorcroft wants to merge 47 commits into
Conversation
Add AgenticComponentActivity to the IR: a generic passthrough for source activities the typed engine cannot express, carrying authored bundle files, resources and a raw Databricks task fragment. Wire it through the workflow preparer dispatch, IR (de)serialization and the DAB writer (keeping agentic files within the bundle source tree and honouring write_to_bundle_root), and add a bundle-invariants check that every pipeline task reference resolves. Consolidates PR #91 (generic agentic bundle component passthrough). Co-authored-by: Isaac <no-reply@databricks.com>
Propagate the flowx-enrich skill + discover-chains-into-enrich reframe up the stack. No new skill content on this branch. Co-authored-by: Isaac <no-reply@databricks.com>
…ponent Propagate discover warning/units + enrich edge_type guidance + GA-grounding up the stack. No edits originate on this branch. Co-authored-by: Isaac <no-reply@databricks.com>
…-component Propagate the --adf-source-path warning correction and the enrich GA-grounding rework up the stack. No edits originate on this branch. Co-authored-by: Isaac <no-reply@databricks.com>
…to stack/agentic-component Co-authored-by: Isaac <no-reply@databricks.com>
…stack/agentic-component Co-authored-by: Isaac <no-reply@databricks.com>
…ck/agentic-component Co-authored-by: Isaac <no-reply@databricks.com>
…ease_state examples) into stack/agentic-component Co-authored-by: Isaac <no-reply@databricks.com>
…ssthrough An AgenticComponentActivity's authored task fragment could override depends_on, run_if, timeouts and retries; only task_key was forced. Reject a fragment that sets any flowx-owned field and apply the activity's own fields last, so the agent supplies only what the task runs. Co-authored-by: Isaac <no-reply@databricks.com>
…h motifs, shared emitter) into stack/agentic-component Co-authored-by: Isaac <no-reply@databricks.com>
…o stack/agentic-component Co-authored-by: Isaac <no-reply@databricks.com>
…/routing-agentic Carries #94, #95 and #96 forward: dataset identity fixes, the shared lineage parser, one shared canonical hash (discovery_serde.canonical_sha256), hardened insights validation and enrich wording, and the agentic component resource-key checks. Conflicts kept both wordings: the AGENTS.md discover line matches phase1-integration, and the MCP tool description takes #95's corrected enrich text with this branch's route and fill_agentic entries. README listed source_insights.json twice after the auto-merge; the two lines are combined into one that keeps both descriptions. Co-authored-by: Isaac <no-reply@databricks.com>
…integration Brings in the final heads of #95 (5fa3ea2), #96 (b508ecc) and #97 (7cc139f), so the integration branch carries every Phase 1 PR as validated: insights hardening and the shared canonical hash, the agentic component checks, and the routing replay model. Co-authored-by: Isaac <no-reply@databricks.com>
…s tasks
Two agentic components that author different content at the same file path now fail
before anything is written; the notebook writer used to rename the second file while its
task still pointed at the first, so it ran the other component's code. Identical shared
files still package. Authored paths with a Windows root or drive ("\tmp\x.py",
"C:x.py") are rejected like other escapes. An authored task fragment must carry exactly
one executable payload (a single "<kind>_task" key), so an empty or conflicting fragment
fails at package time instead of at deploy. Each fix has a regression test that fails on
the previous code.
Co-authored-by: Isaac <no-reply@databricks.com>
…agentic-component Co-authored-by: Isaac <no-reply@databricks.com>
… identity and policy
…for serverless tasks
…tic-component Co-authored-by: Isaac <no-reply@databricks.com>
…fore any tests or lint ran. They timed out downloading packages from `pypi-proxy.cloud.databricks.com`, a Databricks-internal mirror that public GitHub runners can't reach. The cause is in this PR: commit 08eb721 rewrote all 708 package URLs in `uv.lock` to point at that mirror, while the base ddc78d5 has none. The workflow's cleanup step only rewrites index URLs ending in `/simple`, so the `/packages/...` download links stayed pointed at the mirror. Rule broken: the committed `uv.lock` must resolve through the public index (pypi.org / files.pythonhosted.org). `uv.lock` is the only lockfile the PR changes, and no other tracked file refers to an internal mirror. Fix: restored `uv.lock` to its ddc78d5 content (`git checkout ddc78d5 -- uv.lock`; staged, not committed). Only the URLs and file sizes had changed: every package name and version is the same, so the lockfile resolves exactly as before. Checks run locally with UV_FROZEN=1, so `uv run` couldn't re-lock against the local index: - `make test`: 1494 passed, 8 skipped. - `make fmt`: ruff made no changes, lint passed, mypy found no issues in 125 files. - `make requirements`: `requirements.txt` unchanged, so CI's freshness check will pass. - `uv.lock` unchanged after all three runs, with 0 mirror URLs
… write authored text as given Codex round 3 on #96: - A spark_python_task, python_wheel_task or spark_jar_task fragment must name its compute (environment_key, job_cluster_key, existing_cluster_id or new_cluster). flowx binds a cluster only for notebooks, so these packaged with no compute and failed at deploy. - An authored file must set exactly one of content or binary_content; a bare path no longer packages as an empty file. - Authored text files are written byte for byte: no added trailing newline and no platform line endings. Generated notebooks keep the trailing newline. Co-authored-by: Isaac <no-reply@databricks.com>
…ubmit/dbt components
… into stack/routing-agentic Conflicts kept both parents' behaviour: - ir_serde: #97's _locate_task split plus #96's placeholder-owned fields for an AgenticComponentActivity replacement. - mcp/server.py, app/README.md: #96's enrich prepare/apply text plus #97's route and fill_agentic entries. - README.md, architecture.mdx: #96's wording. #95's rename reaches #97's own code: the plan, the route audit, routing and their tests now bind to agentic_insights.json and its agentic_insights_sha256 self-hash key (routing read the old key, so after the merge every plan would have recorded a null insights hash). Co-authored-by: Isaac <no-reply@databricks.com>
…gentic Carries #96's tightened agentic-component checks (compute-less Python, JAR, submit and dbt components; undefined job clusters; authored text written as given). No conflicts. Co-authored-by: Isaac <no-reply@databricks.com>
…ields and on-disk path clashes Greg-checklist review of the agentic component (with an independent Codex pass): - A compute key set to null or empty, or more than one compute key, is refused; a list environment_key no longer crashes the writer. - A for_each_task must carry its body task with a task_key, and the body is marked authored so its parameters are written as given. - An authored file entry with a field other than path, content and binary_content is refused. - Authored paths that differ only in letter case, or a file path another file uses as its directory, are refused before anything is written. - The content signature compares the text that lands on disk, so a generated file that would gain a trailing newline no longer matches an authored file without one. Co-authored-by: Isaac <no-reply@databricks.com>
A component merged over a placeholder lost the placeholder's notifications, compute mode, description, lineage and source definition, so the documented "source activity's collapsed notifications" were never added on the merge path. The component now keeps each of those fields the agent leaves out; a value the agent sets wins. Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
…e_mode claim to source metadata
Brings in #96's component contract: unknown keys on components, resources and loop bodies are refused, and so are libraries and existing_cluster_id on a component. Conflicts kept #97's _locate_task and replace_task_by_name split and added #96's inherited placeholder fields to it. The route skill's AgenticComponentActivity example now follows the contract, and a new test shows fill-agentic refuses a component that puts libraries or a cluster on the activity, with nothing stored. Co-authored-by: Isaac <no-reply@databricks.com>
ghanse
left a comment
There was a problem hiding this comment.
The task-ownership rules, collision checks and byte-for-byte writes look solid. Fix the parameter rewrite on nested agent-written loops before merging.
| pipeline_resources.extend(child_prepared.pipeline_resources) | ||
| environments.extend(child_prepared.environments) | ||
|
|
||
| normalize_inner_task_params(inner_tasks) |
There was a problem hiding this comment.
Blocking: normalize_inner_task_params still rewrites the body of an agent-written for_each_task, so {{input.x}} becomes {{job.parameters.x}}, which points at the outer loop's parameters. Skip bodies marked _authored here and at line 191.
# authored: {"table": "{{input.table_name}}"}
# after: {"table": "{{job.parameters.table_name}}"} # wrong loop| def _collect_environments(workflow: PreparedWorkflow) -> list[dict[str, Any]]: | ||
| """Returns every job environment carried by *workflow* and its inner jobs. | ||
|
|
||
| Each entry is an ``{environment_key, spec}`` dict. An environment declared | ||
| more than once with the same key and spec appears once, so it is written | ||
| once per job that uses it. | ||
| """ | ||
| environments: list[dict[str, Any]] = [] | ||
| for current in [workflow, *workflow.inner_workflows]: | ||
| for environment in current.environments: | ||
| if environment not in environments: | ||
| environments.append(environment) | ||
| return environments | ||
|
|
||
|
|
There was a problem hiding this comment.
Environments are written as-is, but the multi-DAG rename only updates paths inside tasks. So ../src/libraries/foo.whl in an environment points at the old location after combine. The PR description already defers this, so a TODO or follow-up issue is enough.
…a ForEach job
Inside a ForEach that runs as its own job, the inner-job pass rewrote the body of an authored
for_each_task too, so {{input.x}} became {{job.parameters.x}} (the outer loop's parameters), and
the scan forwarded x from the outer loop as an inner job parameter. Both now skip bodies marked
_authored. Adds a TODO where environments are collected: the combined multi-DAG rename does not
rewrite ../src paths inside an environment spec.
Co-authored-by: Isaac <no-reply@databricks.com>
…; nest lone loops
Co-authored-by: Isaac <no-reply@databricks.com>
|
Thanks Greg, both addressed in 3406913:
|
Intent
Add
AgenticComponentActivity, the in-engine escape hatch for a source activity the typed engine can't express. The agent authors raw bundle files, Lakeflow pipeline resources, job environments and a Databricks task fragment, and package writes them into the bundle as given. flowx still owns the task's identity, its dependencies and its run policy (D4): those always come from the activity and win over anything the agent wrote.What changed
models/ir.py,ir_serde.py,dab_writer._reconstruct_ir: the new IR type (files,resources,environments,task,raw_definition). It round-trips through the serialized report.ir_serde._find_and_replace_task): when an agentic component replaces a placeholder, the placeholder's identity and run policy always win, including the ADF timeout and retries. These aretask_key,name,depends_on,timeout_seconds,max_retriesandmin_retry_interval_millis. The component also keeps the placeholder's source metadata (notifications,compute_mode,description,data_reads,data_writes,raw_definitionand the like) for each field the agent leaves out; a value the agent sets wins. NotebookActivity merging is unchanged.preparer/activity_preparers/agentic_component.py:FLOWX_OWNED_TASK_FIELDSand rejects a fragment that sets any of them.skills/flowx-convert/sources/adf.md.item, collapsed notifications, a cluster for a notebook that needs one). It never overrides a value the fragment set.*_taskpayload, or that sets a flowx-owned field, at top level or in afor_each_taskbody (the body keeps its owntask_key);for_each_taskwithout a body task carrying atask_key;spark_submit_taskwithoutnew_clusteror ajob_cluster_key;job_cluster_keythat isn't one of the clusters flowx defines;notebook_task.notebook_paththat isn't a non-empty string, orbase_parameters/job_parametersthat aren't mappings;librariesorexisting_cluster_idset on the component instead of in the fragment;src, including Windows drive-only or root-only paths, or that uses backslashes;contentorbinary_content;bundler/dab_writer.py:src(another component's, a generated notebook or a setup notebook). It also stops when two authored paths differ only in letter case or one file path is another file's directory, and when a pipeline resource or environment key is reused with a different definition, or a pipeline key clashes with a job key. Identical duplicates are written once.environment_keyan authored task names must be defined.DabNotebook.authoredflag keeps them undersrc, and their text is written byte for byte, with no added newline and no platform line endings.preparer/activity_preparers/{if_condition,switch,for_each}.py: forward their children's pipeline resources and environments, so a component nested in control flow keeps them.validate/bundle_invariants.py: reportsdangling_pipeline_referencewhen apipeline_taskpoints at a pipeline the bundle doesn't declare.6e29e8a(including feat(adf): save source graphs with dataset lineage at ADF discover #94 and refactor: add source-neutral discovery graph contract and shared lineage core #84) through clean--no-ffmerges, with no conflicts. Nothing in feat: add AgenticComponentActivity for agent-authored bundle components #96 refers toagentic_insights.json,authored_againstor the inventory rebuild, so they needed no changes.uv.lockis identical to feat: add enrich step that records agent-authored insights on the discover inventory #95's.How it was tested
Unit tests:
tests/unit/test_agentic_component.pyandtest_bundle_invariants.pycover:spark_python_taskandpython_wheel_taskcomponents with their environments;The full unit suite (2005 passed, 8 skipped), ruff and mypy pass.
Deterministic output: ADF discover, convert and package on the fixture pipelines produce bundles, metadata and translation reports byte-identical to feat: add enrich step that records agent-authored insights on the discover inventory #95's head
6e29e8a(325 files).End to end: the no-mistakes test step drove the component through the real CLI (discover, convert, merge-agentic, package). The bundle validated with 0 errors against the DAB JSON schema.
Codex cross-review, read-only, three times. Every fix has a regression test that fails on the previous code.
Round 1 found three feat: add AgenticComponentActivity for agent-authored bundle components #96 issues, all fixed in
9e70c54, with63d12adfrom the no-mistakes review:src.Round 2 found three feat: add AgenticComponentActivity for agent-authored bundle components #96 issues, all fixed in
1b5a763:dab_writerimported a private helper, now public ascontent_signature.Deferred: Round 2's fourth finding, namespacing agentic resources in shared multi-DAG Airflow bundles.
Round 3 (on
839b2c6) found five. Three were feat: add AgenticComponentActivity for agent-authored bundle components #96 issues, all fixed in3586b9d:contentnorbinary_contentpackaged as an empty file. It is now refused.The no-mistakes review then tightened the compute rule, in
1151a0eand07eae42:job_cluster_keymust name a cluster flowx defines;spark_submit_taskanddbt_taskneed compute too;spark_submit_taskruns only on a new cluster.Round 3's other two findings join the deferred Airflow namespacing item: environment and pipeline paths that aren't rewritten, and paths that can be rewritten twice.
Round 4, a review in the style of Greg's refactor: add source-neutral discovery graph contract and shared lineage core #84 and feat(adf): save source graphs with dataset lineage at ADF discover #94 reviews (fail closed, no unknown keys riding along, no false links, docs that match the code), plus an independent Codex pass with the same checklist, found seven feat: add AgenticComponentActivity for agent-authored bundle components #96 issues, all fixed test first in
24aa295and6ec268d:for_each_taskwith no body, or a body without atask_key, packaged.encodingflag wrote base64 text).for_each_taskbody inside a component was cleaned up as if generated.The no-mistakes review of that round then refused flowx-owned fields in a loop body, backslash paths, mistyped notebook and parameter fields, unknown resource and component keys, and
librariesorexisting_cluster_idon the component (6701a13,f2c8df7,7e0222d,ca3d7c7). Outside feat: add AgenticComponentActivity for agent-authored bundle components #96 and left as follow-ups: an unknown IRtypereloads as a placeholder, andmain's generic merge drops source metadata for other replacement types.The no-mistakes review then tightened the contract:
38531a4,9234665,b99d98band1236e5c.38531a4andbb488ec.bf32de8, the captain's choice.a9ab36f,ddbba41and540efe3.Merge order
Merge #84 first, then #94 retargeted to
main, then #95, then this PR (retargeted tomainonce #95 has merged), then #97.This pull request and its description were written by Isaac.