agi-app-multi-dag 2026.5.30__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. agi_app_multi_dag-2026.5.30/LICENSE +22 -0
  2. agi_app_multi_dag-2026.5.30/PKG-INFO +93 -0
  3. agi_app_multi_dag-2026.5.30/README.md +63 -0
  4. agi_app_multi_dag-2026.5.30/pyproject.toml +94 -0
  5. agi_app_multi_dag-2026.5.30/setup.cfg +4 -0
  6. agi_app_multi_dag-2026.5.30/setup.py +47 -0
  7. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/__init__.py +32 -0
  8. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/README.md +52 -0
  9. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/dag_templates/flight_to_weather_legacy_multi_app_dag.json +51 -0
  10. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/dag_templates/flight_to_weather_multi_app_dag.json +51 -0
  11. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/lab_stages.toml +13 -0
  12. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/pyproject.toml +10 -0
  13. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/app_args_form.py +115 -0
  14. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/app_settings.toml +21 -0
  15. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/multi_app_dag/__init__.py +11 -0
  16. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/multi_app_dag/app_args.py +64 -0
  17. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/multi_app_dag/multi_app_dag.py +79 -0
  18. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/multi_app_dag/multi_app_dag_args.py +23 -0
  19. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/multi_app_dag/preview_multi_app_dag.py +213 -0
  20. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/multi_app_dag_worker/__init__.py +3 -0
  21. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/multi_app_dag_worker/multi_app_dag_worker.py +9 -0
  22. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/multi_app_dag_worker/pyproject.toml +9 -0
  23. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/pre_prompt.json +10 -0
  24. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/PKG-INFO +93 -0
  25. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/SOURCES.txt +11 -0
  26. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/dependency_links.txt +1 -0
  27. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/entry_points.txt +3 -0
  28. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/requires.txt +1 -0
  29. agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/top_level.txt +1 -0
@@ -0,0 +1,22 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Jean-Pierre Morard, THALES SIX GTS France SAS
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+ following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
10
+ disclaimer.
11
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12
+ disclaimer in the documentation and/or other materials provided with the distribution.
13
+ 3. Neither the name of Jean-Pierre MORARD nor the names of its contributors, or THALES SIX GTS France SAS, may be used
14
+ to endorse or promote products derived from this software without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
17
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: agi-app-multi-dag
3
+ Version: 2026.5.30
4
+ Summary: AGILAB cross-app DAG preview showing artifact handoffs between demo projects
5
+ Author: Jean-Pierre Morard
6
+ Maintainer: Jean-Pierre Morard
7
+ License-Expression: BSD-3-Clause
8
+ Project-URL: Documentation, https://thalesgroup.github.io/agilab
9
+ Project-URL: Source, https://github.com/ThalesGroup/agilab/tree/main/src/agilab/lib/agi-app-multi-dag
10
+ Project-URL: Issues, https://github.com/ThalesGroup/agilab/issues
11
+ Project-URL: Homepage, https://github.com/ThalesGroup/agilab
12
+ Project-URL: Repository, https://github.com/ThalesGroup/agilab
13
+ Project-URL: Discussions, https://github.com/ThalesGroup/agilab/discussions
14
+ Project-URL: Changelog, https://github.com/ThalesGroup/agilab/releases
15
+ Keywords: agilab,apps,reproducibility,workflow-orchestration
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Development Status :: 4 - Beta
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Operating System :: MacOS
23
+ Classifier: Operating System :: Microsoft :: Windows
24
+ Classifier: Operating System :: POSIX :: Linux
25
+ Requires-Python: >=3.11
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: agi-core<2027.0,>=2026.05.13
29
+ Dynamic: license-file
30
+
31
+ # agi-app-multi-dag
32
+
33
+ [![PyPI version](https://img.shields.io/pypi/v/agi-app-multi-dag.svg?cacheSeconds=300)](https://pypi.org/project/agi-app-multi-dag/)
34
+ [![Python versions](https://img.shields.io/pypi/pyversions/agi-app-multi-dag.svg)](https://pypi.org/project/agi-app-multi-dag/)
35
+ [![License: BSD 3-Clause](https://img.shields.io/pypi/l/agi-app-multi-dag)](https://opensource.org/licenses/BSD-3-Clause)
36
+
37
+ `agi-app-multi-dag` publishes the `multi_app_dag_project` AGILAB app as a
38
+ self-contained PyPI payload. It is a read-only workflow-contract example rather
39
+ than a domain worker benchmark.
40
+
41
+ ## Purpose
42
+
43
+ Use this package to understand how AGILAB can connect several app projects with
44
+ explicit artifact handoffs. The bundled DAG shows a flight stage producing a
45
+ summary artifact that a weather stage can consume.
46
+
47
+ ## Installed Project
48
+
49
+ The distribution name is `agi-app-multi-dag`; the AGILAB project name is
50
+ `multi_app_dag_project`. The package exposes both `multi_app_dag` and
51
+ `multi_app_dag_project` through the `agilab.apps` entry point group, so
52
+ `AgiEnv(app="multi_app_dag_project")` resolves the project without a monorepo
53
+ checkout.
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install agi-app-multi-dag
59
+ ```
60
+
61
+ Most users get this package through `agi-apps`, `agilab[ui]`, or
62
+ `agilab[examples]`; direct installation is useful when validating one app
63
+ package in isolation.
64
+
65
+ ## Run In AGILAB
66
+
67
+ Select `multi_app_dag_project`, open `WORKFLOW`, choose the multi-app DAG template,
68
+ and inspect the runner-state preview. This package is primarily for workflow
69
+ review; use `flight_telemetry_project` or `weather_forecast_project` when you
70
+ want to execute concrete worker code.
71
+
72
+ ## Expected Inputs
73
+
74
+ The package ships a DAG template that names the producer and consumer projects.
75
+ No private data, cluster, service mode, or live external service is required for
76
+ the default preview.
77
+
78
+ ## Expected Outputs
79
+
80
+ The preview writes a runner-state JSON file under the AGILAB execution logs and
81
+ renders the runnable versus blocked stages. It does not create a domain reducer
82
+ artifact because it demonstrates cross-app orchestration, not a worker merge.
83
+
84
+ ## Change One Thing
85
+
86
+ Edit a handoff name in a copied DAG template and reload the preview. The blocked
87
+ stage should make the missing artifact contract obvious before any downstream
88
+ execution is attempted.
89
+
90
+ ## Scope
91
+
92
+ This package teaches multi-app DAG contracts. It is not a scheduler replacement,
93
+ an Airflow/Kubeflow clone, or a production workflow governance layer.
@@ -0,0 +1,63 @@
1
+ # agi-app-multi-dag
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/agi-app-multi-dag.svg?cacheSeconds=300)](https://pypi.org/project/agi-app-multi-dag/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/agi-app-multi-dag.svg)](https://pypi.org/project/agi-app-multi-dag/)
5
+ [![License: BSD 3-Clause](https://img.shields.io/pypi/l/agi-app-multi-dag)](https://opensource.org/licenses/BSD-3-Clause)
6
+
7
+ `agi-app-multi-dag` publishes the `multi_app_dag_project` AGILAB app as a
8
+ self-contained PyPI payload. It is a read-only workflow-contract example rather
9
+ than a domain worker benchmark.
10
+
11
+ ## Purpose
12
+
13
+ Use this package to understand how AGILAB can connect several app projects with
14
+ explicit artifact handoffs. The bundled DAG shows a flight stage producing a
15
+ summary artifact that a weather stage can consume.
16
+
17
+ ## Installed Project
18
+
19
+ The distribution name is `agi-app-multi-dag`; the AGILAB project name is
20
+ `multi_app_dag_project`. The package exposes both `multi_app_dag` and
21
+ `multi_app_dag_project` through the `agilab.apps` entry point group, so
22
+ `AgiEnv(app="multi_app_dag_project")` resolves the project without a monorepo
23
+ checkout.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install agi-app-multi-dag
29
+ ```
30
+
31
+ Most users get this package through `agi-apps`, `agilab[ui]`, or
32
+ `agilab[examples]`; direct installation is useful when validating one app
33
+ package in isolation.
34
+
35
+ ## Run In AGILAB
36
+
37
+ Select `multi_app_dag_project`, open `WORKFLOW`, choose the multi-app DAG template,
38
+ and inspect the runner-state preview. This package is primarily for workflow
39
+ review; use `flight_telemetry_project` or `weather_forecast_project` when you
40
+ want to execute concrete worker code.
41
+
42
+ ## Expected Inputs
43
+
44
+ The package ships a DAG template that names the producer and consumer projects.
45
+ No private data, cluster, service mode, or live external service is required for
46
+ the default preview.
47
+
48
+ ## Expected Outputs
49
+
50
+ The preview writes a runner-state JSON file under the AGILAB execution logs and
51
+ renders the runnable versus blocked stages. It does not create a domain reducer
52
+ artifact because it demonstrates cross-app orchestration, not a worker merge.
53
+
54
+ ## Change One Thing
55
+
56
+ Edit a handoff name in a copied DAG template and reload the preview. The blocked
57
+ stage should make the missing artifact contract obvious before any downstream
58
+ execution is attempted.
59
+
60
+ ## Scope
61
+
62
+ This package teaches multi-app DAG contracts. It is not a scheduler replacement,
63
+ an Airflow/Kubeflow clone, or a production workflow governance layer.
@@ -0,0 +1,94 @@
1
+ [project]
2
+ version = "2026.05.30"
3
+ name = "agi-app-multi-dag"
4
+ description = "AGILAB cross-app DAG preview showing artifact handoffs between demo projects"
5
+ requires-python = ">=3.11"
6
+ readme = "README.md"
7
+ authors = [
8
+ { name = "Jean-Pierre Morard" }
9
+ ]
10
+ maintainers = [{ name = "Jean-Pierre Morard" }]
11
+ license = "BSD-3-Clause"
12
+ license-files = ["LICENSE"]
13
+
14
+ classifiers = [
15
+ "Intended Audience :: Developers",
16
+ "Development Status :: 4 - Beta",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Operating System :: MacOS",
22
+ "Operating System :: Microsoft :: Windows",
23
+ "Operating System :: POSIX :: Linux",
24
+ ]
25
+
26
+ keywords = [
27
+ "agilab",
28
+ "apps",
29
+ "reproducibility",
30
+ "workflow-orchestration",
31
+ ]
32
+
33
+ dependencies = ["agi-core>=2026.05.13,<2027.0"]
34
+
35
+ [project.urls]
36
+ Documentation = "https://thalesgroup.github.io/agilab"
37
+ Source = "https://github.com/ThalesGroup/agilab/tree/main/src/agilab/lib/agi-app-multi-dag"
38
+ Issues = "https://github.com/ThalesGroup/agilab/issues"
39
+ Homepage = "https://github.com/ThalesGroup/agilab"
40
+ Repository = "https://github.com/ThalesGroup/agilab"
41
+ Discussions = "https://github.com/ThalesGroup/agilab/discussions"
42
+ Changelog = "https://github.com/ThalesGroup/agilab/releases"
43
+
44
+ [project.entry-points."agilab.apps"]
45
+ multi_app_dag = "agi_app_multi_dag:project_root"
46
+ multi_app_dag_project = "agi_app_multi_dag:project_root"
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+ [dependency-groups]
59
+ dev = [
60
+ "pytest",
61
+ ]
62
+
63
+ [tool.uv.sources.agi-core]
64
+ path = "../../core/agi-core"
65
+ editable = true
66
+
67
+ [build-system]
68
+ requires = ["setuptools>=68", "wheel"]
69
+ build-backend = "setuptools.build_meta"
70
+
71
+ [tool.setuptools]
72
+ include-package-data = false
73
+ package-dir = {"" = "src"}
74
+ packages = ["agi_app_multi_dag"]
75
+
76
+ [tool.setuptools.package-data]
77
+ "agi_app_multi_dag" = [
78
+ "project/**/*",
79
+ ]
80
+
81
+ [tool.setuptools.exclude-package-data]
82
+ "agi_app_multi_dag" = [
83
+ "project/**/.venv/**",
84
+ "project/**/__pycache__/**",
85
+ "project/**/*.pyc",
86
+ "project/**/*.pyo",
87
+ "project/**/*.pyx",
88
+ "project/**/*.c",
89
+ "project/**/*.so",
90
+ "project/**/uv.lock",
91
+ ]
92
+
93
+ [tool.pytest.ini_options]
94
+ testpaths = ["test"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.util
4
+ from pathlib import Path
5
+
6
+ from setuptools import setup
7
+ from setuptools.command.build_py import build_py as _build_py
8
+ from setuptools.command.sdist import sdist as _sdist
9
+
10
+ APP_PROJECT = 'multi_app_dag_project'
11
+ PACKAGE_IMPORT = 'agi_app_multi_dag'
12
+
13
+
14
+ def _load_build_support():
15
+ module_path = Path(__file__).resolve().parents[4] / "src" / "agilab" / "lib" / "app_project_build_support.py"
16
+ if not module_path.exists():
17
+ return None
18
+ spec = importlib.util.spec_from_file_location("agilab_app_project_build_support", module_path)
19
+ if spec is None or spec.loader is None:
20
+ raise RuntimeError(f"Unable to load app project build support from {module_path}")
21
+ module = importlib.util.module_from_spec(spec)
22
+ spec.loader.exec_module(module)
23
+ return module
24
+
25
+
26
+ def _copy_payload(target_root: Path) -> None:
27
+ support = _load_build_support()
28
+ if support is None:
29
+ return
30
+ changed = support.copy_app_project_payload(APP_PROJECT, target_root)
31
+ for pyproject_path in changed:
32
+ print(f"[{PACKAGE_IMPORT}] sanitized packaged app manifest: {pyproject_path}")
33
+
34
+
35
+ class build_py(_build_py):
36
+ def run(self):
37
+ super().run()
38
+ _copy_payload(Path(self.build_lib) / PACKAGE_IMPORT / "project")
39
+
40
+
41
+ class sdist(_sdist):
42
+ def make_release_tree(self, base_dir, files):
43
+ super().make_release_tree(base_dir, files)
44
+ _copy_payload(Path(base_dir) / "src" / PACKAGE_IMPORT / "project")
45
+
46
+
47
+ setup(cmdclass={"build_py": build_py, "sdist": sdist})
@@ -0,0 +1,32 @@
1
+ """Installed AGILAB app project provider for multi_app_dag_project."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ APP_SLUG = 'multi_app_dag'
8
+ PROJECT_NAME = 'multi_app_dag_project'
9
+ PACKAGE_NAME = 'agi-app-multi-dag'
10
+
11
+
12
+ def package_root() -> Path:
13
+ return Path(__file__).resolve().parent
14
+
15
+
16
+ def project_root() -> Path:
17
+ source_root = Path(__file__).resolve().parents[4] / "apps" / "builtin" / PROJECT_NAME
18
+ if source_root.exists():
19
+ return source_root
20
+ return package_root() / "project" / PROJECT_NAME
21
+
22
+
23
+ def metadata() -> dict[str, str]:
24
+ return {
25
+ "slug": APP_SLUG,
26
+ "project": PROJECT_NAME,
27
+ "package": PACKAGE_NAME,
28
+ "project_root": str(project_root()),
29
+ }
30
+
31
+
32
+ __all__ = ["APP_SLUG", "PACKAGE_NAME", "PROJECT_NAME", "metadata", "package_root", "project_root"]
@@ -0,0 +1,52 @@
1
+ # Multi-app DAG Project
2
+
3
+ `multi_app_dag_project` is the built-in read-only example for cross-app DAG
4
+ contracts.
5
+
6
+ ## Purpose
7
+
8
+ Use this project to inspect how independent AGILAB apps can be connected through
9
+ explicit artifact handoffs before any app is executed.
10
+
11
+ ## What You Learn
12
+
13
+ - How a DAG template can live with the app that teaches it.
14
+ - How `flight_telemetry_project` can produce a handoff consumed by a weather
15
+ stage.
16
+ - How runner-state previews expose runnable and blocked units.
17
+ - Why cross-app orchestration should keep artifact contracts explicit.
18
+
19
+ ## Run In AGILAB
20
+
21
+ 1. Select `multi_app_dag_project` in `WORKFLOW`.
22
+ 2. Open `Workflow graph`.
23
+ 3. Choose `Multi-app DAG`.
24
+ 4. Select the bundled flight-to-weather DAG template.
25
+ 5. Inspect stage readiness and handoffs.
26
+
27
+ ## Expected Inputs
28
+
29
+ The project ships DAG templates under `dag_templates/`. No live app execution is
30
+ required for the preview.
31
+
32
+ ## Expected Outputs
33
+
34
+ The preview writes runner-state JSON under `~/log/execute/multi_app_dag/` and
35
+ reports app stages, artifact dependencies, and dispatch readiness.
36
+
37
+ ## Change One Thing
38
+
39
+ After the default preview works, edit a copy of the DAG template and change one
40
+ artifact handoff. The preview should make the affected downstream stage blocked
41
+ or runnable based on that contract.
42
+
43
+ ## Troubleshooting
44
+
45
+ If the preview cannot find built-in apps, run AGILAB once from the source or
46
+ installed checkout so `.agilab-path` points at the package. If packaged preview
47
+ layout setup fails, inspect `~/.cache/agilab/multi_app_dag_layout`.
48
+
49
+ ## Scope
50
+
51
+ This is a DAG-contract preview. It intentionally does not ship a reducer
52
+ contract because it does not execute a concrete worker merge output.
@@ -0,0 +1,51 @@
1
+ {
2
+ "schema": "agilab.multi_app_dag.v1",
3
+ "dag_id": "flight-to-meteo-multi-app-dag-example",
4
+ "label": "Flight to meteo multi-app DAG handoff",
5
+ "description": "Two public AGILAB projects connected through an explicit multi-app DAG artifact contract. The preview plans the DAG and runner state; it does not execute either app.",
6
+ "execution": {
7
+ "mode": "sequential_dependency_order",
8
+ "runner_status": "contract_only"
9
+ },
10
+ "nodes": [
11
+ {
12
+ "id": "flight_context",
13
+ "app": "flight_telemetry_project",
14
+ "purpose": "Run the flight scenario and produce trajectory summary context for downstream review.",
15
+ "produces": [
16
+ {
17
+ "id": "flight_reduce_summary",
18
+ "kind": "reduce_summary",
19
+ "path": "flight_analysis/reduce_summary_worker_0.json"
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "id": "weather_forecast_legacy_review",
25
+ "app": "weather_forecast_legacy_project",
26
+ "purpose": "Consume the flight summary context and produce forecast quality metrics.",
27
+ "consumes": [
28
+ {
29
+ "id": "flight_reduce_summary",
30
+ "kind": "reduce_summary",
31
+ "path": "flight_analysis/reduce_summary_worker_0.json"
32
+ }
33
+ ],
34
+ "produces": [
35
+ {
36
+ "id": "forecast_metrics",
37
+ "kind": "summary_metrics",
38
+ "path": "forecast_analysis/forecast_metrics.json"
39
+ }
40
+ ]
41
+ }
42
+ ],
43
+ "edges": [
44
+ {
45
+ "from": "flight_context",
46
+ "to": "weather_forecast_legacy_review",
47
+ "artifact": "flight_reduce_summary",
48
+ "handoff": "Use flight trajectory reduce summary as the forecast-review context."
49
+ }
50
+ ]
51
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "schema": "agilab.multi_app_dag.v1",
3
+ "dag_id": "flight-to-weather-multi-app-dag-example",
4
+ "label": "Flight to weather multi-app DAG handoff",
5
+ "description": "Two public AGILAB projects connected through an explicit multi-app DAG artifact contract. The preview plans the DAG and runner state; it does not execute either app.",
6
+ "execution": {
7
+ "mode": "sequential_dependency_order",
8
+ "runner_status": "contract_only"
9
+ },
10
+ "nodes": [
11
+ {
12
+ "id": "flight_context",
13
+ "app": "flight_telemetry_project",
14
+ "purpose": "Run the flight scenario and produce trajectory summary context for downstream review.",
15
+ "produces": [
16
+ {
17
+ "id": "flight_reduce_summary",
18
+ "kind": "reduce_summary",
19
+ "path": "flight_analysis/reduce_summary_worker_0.json"
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "id": "weather_forecast_review",
25
+ "app": "weather_forecast_project",
26
+ "purpose": "Consume the flight summary context and produce forecast quality metrics.",
27
+ "consumes": [
28
+ {
29
+ "id": "flight_reduce_summary",
30
+ "kind": "reduce_summary",
31
+ "path": "flight_analysis/reduce_summary_worker_0.json"
32
+ }
33
+ ],
34
+ "produces": [
35
+ {
36
+ "id": "forecast_metrics",
37
+ "kind": "summary_metrics",
38
+ "path": "forecast_analysis/forecast_metrics.json"
39
+ }
40
+ ]
41
+ }
42
+ ],
43
+ "edges": [
44
+ {
45
+ "from": "flight_context",
46
+ "to": "weather_forecast_review",
47
+ "artifact": "flight_reduce_summary",
48
+ "handoff": "Use flight trajectory reduce summary as the forecast-review context."
49
+ }
50
+ ]
51
+ }
@@ -0,0 +1,13 @@
1
+ [[steps]]
2
+ D = "Inspect the multi-app DAG contract"
3
+ Q = "Open the bundled flight-to-meteo DAG and identify stages, artifacts, and handoffs."
4
+ M = ""
5
+ C = "from pathlib import Path\nimport json\npath = Path('dag_templates/flight_to_weather_legacy_multi_app_dag.json')\npayload = json.loads(path.read_text(encoding='utf-8'))\n[(node['id'], node['app']) for node in payload['nodes']]"
6
+ R = "runpy"
7
+
8
+ [[steps]]
9
+ D = "Preview runner state"
10
+ Q = "Build a read-only runner-state preview without executing either app."
11
+ M = ""
12
+ C = "from pathlib import Path\nfrom multi_app_dag.preview_multi_app_dag import build_preview, planning_repo_root\nsummary = build_preview(repo_root=planning_repo_root(None), output_path=Path.home() / 'log/execute/multi_app_dag/runner_state.json')\nsummary['runner_state']['summary']"
13
+ R = "runpy"
@@ -0,0 +1,10 @@
1
+ [project]
2
+ name = "multi_app_dag_project"
3
+ version = "2026.05.30"
4
+ description = "Built-in AGILAB multi-app DAG example project"
5
+ requires-python = ">=3.11"
6
+ dependencies = ["agi-env>=2026.05.30", "agi-node>=2026.05.30", "agi-cluster>=2026.05.30", "polars>=1.35,<2", "pydantic>=2.11,<2.13", "streamlit>=1.56,<1.57"]
7
+
8
+ [build-system]
9
+ requires = ["setuptools"]
10
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import sys
5
+ from typing import Any
6
+
7
+ import streamlit as st
8
+ from pydantic import ValidationError
9
+
10
+ _HERE = Path(__file__).resolve().parent
11
+
12
+
13
+ def _ensure_project_src_import_precedence() -> None:
14
+ """Keep the project src ahead of the package directory for Streamlit form imports."""
15
+
16
+ project_src = str(_HERE)
17
+ sys.path[:] = [entry for entry in sys.path if entry != project_src]
18
+ sys.path.insert(0, project_src)
19
+
20
+ module = sys.modules.get("multi_app_dag")
21
+ if module is None or getattr(module, "__path__", None):
22
+ return
23
+ for loaded_name in list(sys.modules):
24
+ if loaded_name == "multi_app_dag" or loaded_name.startswith("multi_app_dag."):
25
+ sys.modules.pop(loaded_name, None)
26
+
27
+
28
+ _ensure_project_src_import_precedence()
29
+
30
+ from multi_app_dag.app_args import MultiAppDagArgs, dump_args, load_args # noqa: E402
31
+
32
+
33
+ PAGE_ID = "multi_app_dag_project:app_args_form"
34
+
35
+
36
+ def _k(name: str) -> str:
37
+ return f"{PAGE_ID}:{name}"
38
+
39
+
40
+ def _get_env():
41
+ env = st.session_state.get("env") or st.session_state.get("_env")
42
+ if env is None:
43
+ st.error("AGILAB environment is not initialised yet. Return to the main page and try again.")
44
+ st.stop()
45
+ return env
46
+
47
+
48
+ def _load_current_args(settings_path: Path) -> MultiAppDagArgs:
49
+ try:
50
+ return load_args(settings_path)
51
+ except Exception as exc:
52
+ st.warning(f"Unable to load Multi-app DAG args from `{settings_path}`: {exc}")
53
+ return MultiAppDagArgs()
54
+
55
+
56
+ env = _get_env()
57
+ settings_path = Path(env.app_settings_file)
58
+ current_args = _load_current_args(settings_path)
59
+ current_payload = current_args.model_dump(mode="json")
60
+
61
+ st.caption(
62
+ "Multi-app DAG is a planning project: edit the template path and preview output, "
63
+ "then use WORKFLOW to inspect the cross-app DAG."
64
+ )
65
+
66
+ for key, default in (
67
+ ("dag_path", str(current_payload.get("dag_path", "dag_templates/flight_to_weather_legacy_multi_app_dag.json"))),
68
+ ("output_path", str(current_payload.get("output_path", "~/log/execute/multi_app_dag/runner_state.json"))),
69
+ ("reset_target", bool(current_payload.get("reset_target", False))),
70
+ ):
71
+ st.session_state.setdefault(_k(key), default)
72
+
73
+ c1, c2 = st.columns([2, 2])
74
+ with c1:
75
+ st.text_input("DAG template", key=_k("dag_path"))
76
+ with c2:
77
+ st.text_input("Preview output", key=_k("output_path"))
78
+
79
+ c3, _ = st.columns([1.2, 2.8])
80
+ with c3:
81
+ st.checkbox("Reset preview output", key=_k("reset_target"))
82
+
83
+ candidate: dict[str, Any] = {
84
+ "dag_path": (st.session_state.get(_k("dag_path")) or "").strip(),
85
+ "output_path": (st.session_state.get(_k("output_path")) or "").strip(),
86
+ "reset_target": bool(st.session_state.get(_k("reset_target"), False)),
87
+ }
88
+
89
+ try:
90
+ validated = MultiAppDagArgs(**candidate)
91
+ except ValidationError as exc:
92
+ st.error("Invalid Multi-app DAG parameters:")
93
+ if hasattr(env, "humanize_validation_errors"):
94
+ for msg in env.humanize_validation_errors(exc):
95
+ st.markdown(msg)
96
+ else:
97
+ st.code(str(exc))
98
+ else:
99
+ validated_payload = validated.model_dump(mode="json")
100
+ if validated_payload != current_payload:
101
+ dump_args(validated, settings_path)
102
+ app_settings = st.session_state.get("app_settings")
103
+ if not isinstance(app_settings, dict):
104
+ app_settings = {}
105
+ app_settings.setdefault("cluster", {})
106
+ app_settings["args"] = validated_payload
107
+ st.session_state["app_settings"] = app_settings
108
+ st.session_state["is_args_from_ui"] = True
109
+ st.success(f"Saved to `{settings_path}`.")
110
+ else:
111
+ st.info("No changes to save.")
112
+
113
+ st.caption(
114
+ f"Template `{validated.dag_path}` will write preview state to `{validated.output_path}`."
115
+ )
@@ -0,0 +1,21 @@
1
+ [args]
2
+ dag_path = "dag_templates/flight_to_weather_legacy_multi_app_dag.json"
3
+ output_path = "~/log/execute/multi_app_dag/runner_state.json"
4
+ reset_target = false
5
+
6
+ [cluster]
7
+ verbose = 1
8
+ cython = false
9
+ pool = false
10
+ rapids = false
11
+ cluster_enabled = false
12
+ scheduler = "127.0.0.1:8786"
13
+ workers_data_path = ""
14
+
15
+ [cluster.workers]
16
+ "127.0.0.1" = 2
17
+
18
+ [cluster.service_health]
19
+ allow_idle = false
20
+ max_unhealthy = 0
21
+ max_restart_rate = 0.25
@@ -0,0 +1,11 @@
1
+ from .app_args import ArgsModel, MultiAppDagArgs, dump_args, load_args
2
+
3
+ __all__ = ["ArgsModel", "MultiAppDag", "MultiAppDagApp", "MultiAppDagArgs", "dump_args", "load_args"]
4
+
5
+
6
+ def __getattr__(name: str):
7
+ if name in {"MultiAppDag", "MultiAppDagApp"}:
8
+ from .multi_app_dag import MultiAppDag, MultiAppDagApp
9
+
10
+ return {"MultiAppDag": MultiAppDag, "MultiAppDagApp": MultiAppDagApp}[name]
11
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,64 @@
1
+ """Argument helpers for the built-in multi-app DAG project."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any, TypedDict
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+ from agi_env.app_args import dump_model_to_toml, load_model_from_toml, merge_model_data
11
+
12
+
13
+ class MultiAppDagArgs(BaseModel):
14
+ """Runtime parameters for the multi-app DAG preview project."""
15
+
16
+ model_config = ConfigDict(extra="forbid")
17
+
18
+ dag_path: Path = Field(default_factory=lambda: Path("dag_templates/flight_to_weather_legacy_multi_app_dag.json"))
19
+ output_path: Path = Field(default_factory=lambda: Path("~/log/execute/multi_app_dag/runner_state.json"))
20
+ reset_target: bool = False
21
+
22
+
23
+ class MultiAppDagArgsTD(TypedDict, total=False):
24
+ dag_path: str
25
+ output_path: str
26
+ reset_target: bool
27
+
28
+
29
+ ArgsModel = MultiAppDagArgs
30
+ ArgsOverrides = MultiAppDagArgsTD
31
+
32
+
33
+ def load_args(settings_path: str | Path, *, section: str = "args") -> MultiAppDagArgs:
34
+ return load_model_from_toml(MultiAppDagArgs, settings_path, section=section)
35
+
36
+
37
+ def merge_args(base: MultiAppDagArgs, overrides: MultiAppDagArgsTD | None = None) -> MultiAppDagArgs:
38
+ return merge_model_data(base, overrides)
39
+
40
+
41
+ def dump_args(
42
+ args: MultiAppDagArgs,
43
+ settings_path: str | Path,
44
+ *,
45
+ section: str = "args",
46
+ create_missing: bool = True,
47
+ ) -> None:
48
+ dump_model_to_toml(args, settings_path, section=section, create_missing=create_missing)
49
+
50
+
51
+ def ensure_defaults(args: MultiAppDagArgs, **_: Any) -> MultiAppDagArgs:
52
+ return args
53
+
54
+
55
+ __all__ = [
56
+ "ArgsModel",
57
+ "ArgsOverrides",
58
+ "MultiAppDagArgs",
59
+ "MultiAppDagArgsTD",
60
+ "dump_args",
61
+ "ensure_defaults",
62
+ "load_args",
63
+ "merge_args",
64
+ ]
@@ -0,0 +1,79 @@
1
+ """Manager surface for the built-in multi-app DAG project."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from pydantic import ValidationError
9
+
10
+ from agi_node.agi_dispatcher import BaseWorker, WorkDispatcher
11
+
12
+ from .app_args import ArgsOverrides, MultiAppDagArgs, dump_args, ensure_defaults, load_args, merge_args
13
+ from .preview_multi_app_dag import PROJECT_ROOT, build_preview, planning_repo_root
14
+
15
+
16
+ class MultiAppDag(BaseWorker):
17
+ """Planning-only manager for built-in multi-app DAG templates."""
18
+
19
+ worker_vars: dict[str, Any] = {}
20
+
21
+ def __init__(
22
+ self,
23
+ env,
24
+ args: MultiAppDagArgs | None = None,
25
+ **kwargs: ArgsOverrides,
26
+ ) -> None:
27
+ self.env = env
28
+ self.verbose = int(kwargs.pop("verbose", getattr(env, "verbose", 0) or 0))
29
+ if args is None:
30
+ try:
31
+ args = MultiAppDagArgs(**kwargs)
32
+ except ValidationError as exc:
33
+ raise ValueError(f"Invalid Multi-app DAG arguments: {exc}") from exc
34
+ self.args = ensure_defaults(args, env=env)
35
+ @classmethod
36
+ def from_toml(
37
+ cls,
38
+ env,
39
+ settings_path: str | Path = "app_settings.toml",
40
+ section: str = "args",
41
+ **overrides: ArgsOverrides,
42
+ ) -> "MultiAppDag":
43
+ base = load_args(settings_path, section=section)
44
+ merged = ensure_defaults(merge_args(base, overrides or None), env=env)
45
+ return cls(env, args=merged)
46
+
47
+ def to_toml(
48
+ self,
49
+ settings_path: str | Path = "app_settings.toml",
50
+ section: str = "args",
51
+ create_missing: bool = True,
52
+ ) -> None:
53
+ dump_args(self.args, settings_path, section=section, create_missing=create_missing)
54
+
55
+ def preview(self) -> dict[str, Any]:
56
+ dag_path = self.args.dag_path.expanduser()
57
+ if not dag_path.is_absolute():
58
+ dag_path = PROJECT_ROOT / dag_path
59
+ return build_preview(
60
+ repo_root=planning_repo_root(None),
61
+ dag_path=dag_path,
62
+ output_path=self.args.output_path.expanduser(),
63
+ )
64
+
65
+ def as_dict(self) -> dict[str, Any]:
66
+ return self.args.model_dump(mode="json")
67
+
68
+ def build_distribution(self, _workers: dict | None = None):
69
+ return [], [], "stage", "weight", ""
70
+
71
+ def stop(self) -> None:
72
+ super().stop()
73
+
74
+
75
+ class MultiAppDagApp(MultiAppDag):
76
+ """Compatibility alias retaining the app suffix."""
77
+
78
+
79
+ __all__ = ["MultiAppDag", "MultiAppDagApp"]
@@ -0,0 +1,23 @@
1
+ """Compatibility import surface for the PROJECT page argument editor."""
2
+
3
+ from .app_args import (
4
+ ArgsModel,
5
+ ArgsOverrides,
6
+ MultiAppDagArgs,
7
+ MultiAppDagArgsTD,
8
+ dump_args,
9
+ ensure_defaults,
10
+ load_args,
11
+ merge_args,
12
+ )
13
+
14
+ __all__ = [
15
+ "ArgsModel",
16
+ "ArgsOverrides",
17
+ "MultiAppDagArgs",
18
+ "MultiAppDagArgsTD",
19
+ "dump_args",
20
+ "ensure_defaults",
21
+ "load_args",
22
+ "merge_args",
23
+ ]
@@ -0,0 +1,213 @@
1
+ """Read-only preview for the built-in multi-app DAG project."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import shutil
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any, Sequence
11
+
12
+
13
+ def _repo_root_from_file() -> Path | None:
14
+ for parent in Path(__file__).resolve().parents:
15
+ if (parent / "src" / "agilab" / "apps" / "builtin").is_dir():
16
+ return parent
17
+ return None
18
+
19
+
20
+ _SOURCE_ROOT = _repo_root_from_file()
21
+ if _SOURCE_ROOT is not None:
22
+ source_path = str(_SOURCE_ROOT / "src")
23
+ if source_path not in sys.path:
24
+ sys.path.insert(0, source_path)
25
+
26
+ from agilab.global_pipeline_execution_plan import build_execution_plan
27
+ from agilab.global_pipeline_runner_state import dispatch_next_runnable, persist_runner_state
28
+
29
+
30
+ PROJECT_ROOT = Path(__file__).resolve().parents[2]
31
+ DAG_PATH = PROJECT_ROOT / "dag_templates" / "flight_to_weather_legacy_multi_app_dag.json"
32
+ DEFAULT_OUTPUT_PATH = Path.home() / "log" / "execute" / "multi_app_dag" / "runner_state.json"
33
+ RUN_ID = "multi-app-dag-preview"
34
+
35
+
36
+ def agilab_package_path() -> Path:
37
+ marker = Path.home() / ".local/share/agilab/.agilab-path"
38
+ if not marker.is_file():
39
+ raise SystemExit(
40
+ "AGILAB is not initialized. Run the AGILAB installer or "
41
+ "`agilab first-proof --json` before this example."
42
+ )
43
+ path = Path(marker.read_text(encoding="utf-8").strip()).expanduser()
44
+ if not path.is_dir():
45
+ raise SystemExit(f"AGILAB package path from {marker} does not exist: {path}")
46
+ return path
47
+
48
+
49
+ def _source_checkout_root_from_package(package_path: Path) -> Path | None:
50
+ parents = package_path.parents
51
+ if len(parents) < 2:
52
+ return None
53
+ repo_root = parents[1]
54
+ if (repo_root / "src" / "agilab" / "apps" / "builtin").is_dir():
55
+ return repo_root
56
+ return None
57
+
58
+
59
+ def _ensure_packaged_layout_adapter(package_path: Path) -> Path:
60
+ apps_path = package_path / "apps"
61
+ if not (apps_path / "builtin").is_dir():
62
+ raise SystemExit(f"AGILAB built-in apps are not available under {apps_path}")
63
+
64
+ repo_root = Path.home() / ".cache" / "agilab" / "multi_app_dag_layout"
65
+ adapter_package = repo_root / "src" / "agilab"
66
+ adapter_apps = adapter_package / "apps"
67
+ adapter_package.mkdir(parents=True, exist_ok=True)
68
+
69
+ if adapter_apps.is_symlink() and adapter_apps.resolve() != apps_path.resolve():
70
+ adapter_apps.unlink()
71
+ if not adapter_apps.exists():
72
+ try:
73
+ adapter_apps.symlink_to(apps_path, target_is_directory=True)
74
+ except OSError:
75
+ shutil.copytree(apps_path, adapter_apps, dirs_exist_ok=True)
76
+ if not (repo_root / "src" / "agilab" / "apps" / "builtin").is_dir():
77
+ raise SystemExit(f"Could not prepare a planning layout under {repo_root}")
78
+ return repo_root
79
+
80
+
81
+ def planning_repo_root(explicit_repo_root: Path | None = None) -> Path:
82
+ if explicit_repo_root is not None:
83
+ return explicit_repo_root.expanduser().resolve()
84
+ if _SOURCE_ROOT is not None:
85
+ return _SOURCE_ROOT.resolve()
86
+ package_path = agilab_package_path()
87
+ source_root = _source_checkout_root_from_package(package_path)
88
+ if source_root is not None:
89
+ return source_root.resolve()
90
+ return _ensure_packaged_layout_adapter(package_path).resolve()
91
+
92
+
93
+ def _unit_preview(unit: dict[str, Any]) -> dict[str, Any]:
94
+ return {
95
+ "id": unit["id"],
96
+ "app": unit["app"],
97
+ "dispatch_status": "runnable" if unit.get("ready") is True else "blocked",
98
+ "depends_on": list(unit.get("depends_on", [])),
99
+ "produces": [
100
+ artifact["artifact"]
101
+ for artifact in unit.get("produces", [])
102
+ if isinstance(artifact, dict) and artifact.get("artifact")
103
+ ],
104
+ }
105
+
106
+
107
+ def _artifact_handoffs(units: Sequence[dict[str, Any]]) -> list[dict[str, str]]:
108
+ handoffs: list[dict[str, str]] = []
109
+ units_by_id = {str(unit.get("id", "")): unit for unit in units}
110
+ for unit in units:
111
+ for dependency in unit.get("artifact_dependencies", []):
112
+ if not isinstance(dependency, dict):
113
+ continue
114
+ source_id = str(dependency.get("from", ""))
115
+ source_unit = units_by_id.get(source_id, {})
116
+ handoffs.append(
117
+ {
118
+ "artifact": str(dependency.get("artifact", "")),
119
+ "from": source_id,
120
+ "from_app": str(dependency.get("from_app", "")),
121
+ "source_path": str(dependency.get("source_path", "")),
122
+ "to": str(unit.get("id", "")),
123
+ "to_app": str(unit.get("app", "")),
124
+ "handoff": str(dependency.get("handoff", "")),
125
+ "producer_status": "runnable" if source_unit.get("ready") is True else "blocked",
126
+ }
127
+ )
128
+ return handoffs
129
+
130
+
131
+ def build_preview(
132
+ *,
133
+ repo_root: Path,
134
+ dag_path: Path = DAG_PATH,
135
+ output_path: Path = DEFAULT_OUTPUT_PATH,
136
+ now: str = "2026-04-29T00:00:00Z",
137
+ ) -> dict[str, Any]:
138
+ plan = build_execution_plan(repo_root=repo_root, dag_path=dag_path)
139
+ proof = persist_runner_state(
140
+ repo_root=repo_root,
141
+ dag_path=dag_path,
142
+ output_path=output_path,
143
+ run_id=RUN_ID,
144
+ now=now,
145
+ )
146
+ dispatch = dispatch_next_runnable(proof.runner_state, now=now)
147
+ units = list(plan.runnable_units)
148
+
149
+ return {
150
+ "example": "multi_app_dag_project",
151
+ "goal": "Preview a built-in multi-app DAG that coordinates multiple AGILAB apps through artifact contracts.",
152
+ "dag": {
153
+ "path": plan.dag_path,
154
+ "ok": plan.ok,
155
+ "issues": [issue.as_dict() for issue in plan.issues],
156
+ "execution_order": list(plan.execution_order),
157
+ },
158
+ "units": [_unit_preview(unit) for unit in units],
159
+ "artifact_handoffs": _artifact_handoffs(units),
160
+ "runner_state": {
161
+ "path": proof.path,
162
+ "round_trip_ok": proof.round_trip_ok,
163
+ "run_status": proof.runner_state["run_status"],
164
+ "summary": proof.runner_state["summary"],
165
+ },
166
+ "after_first_dispatch": {
167
+ "ok": dispatch.ok,
168
+ "dispatched_unit_id": dispatch.dispatched_unit_id,
169
+ "run_status": dispatch.state["run_status"],
170
+ "summary": dispatch.state["summary"],
171
+ },
172
+ "real_app_execution": False,
173
+ }
174
+
175
+
176
+ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
177
+ parser = argparse.ArgumentParser(
178
+ description="Preview the built-in AGILAB multi-app DAG without executing app stages."
179
+ )
180
+ parser.add_argument(
181
+ "--repo-root",
182
+ type=Path,
183
+ default=None,
184
+ help="Optional source checkout root. If omitted, the preview uses the local source layout or AGILAB marker.",
185
+ )
186
+ parser.add_argument(
187
+ "--dag-path",
188
+ type=Path,
189
+ default=DAG_PATH,
190
+ help="Path to an agilab.multi_app_dag.v1 JSON contract.",
191
+ )
192
+ parser.add_argument(
193
+ "--output",
194
+ type=Path,
195
+ default=DEFAULT_OUTPUT_PATH,
196
+ help="Where to write the read-only runner-state preview JSON.",
197
+ )
198
+ return parser.parse_args(argv)
199
+
200
+
201
+ def main(argv: Sequence[str] | None = None) -> dict[str, Any]:
202
+ args = _parse_args(argv)
203
+ summary = build_preview(
204
+ repo_root=planning_repo_root(args.repo_root),
205
+ dag_path=args.dag_path.expanduser(),
206
+ output_path=args.output.expanduser(),
207
+ )
208
+ print(json.dumps(summary, indent=2, sort_keys=True))
209
+ return summary
210
+
211
+
212
+ if __name__ == "__main__":
213
+ main()
@@ -0,0 +1,3 @@
1
+ from .multi_app_dag_worker import MultiAppDagWorker
2
+
3
+ __all__ = ["MultiAppDagWorker"]
@@ -0,0 +1,9 @@
1
+ """No-op worker for the planning-only multi-app DAG project."""
2
+
3
+ from agi_node.polars_worker.polars_worker import PolarsWorker
4
+
5
+
6
+ class MultiAppDagWorker(PolarsWorker):
7
+ """Worker placeholder so the built-in project follows AGILAB app layout."""
8
+
9
+ pass
@@ -0,0 +1,9 @@
1
+ [project]
2
+ name = "multi_app_dag_worker"
3
+ version = "2026.05.30"
4
+ requires-python = ">=3.11"
5
+ dependencies = ["agi-env>=2026.05.30", "agi-node>=2026.05.30", "polars>=1.35,<2"]
6
+
7
+ [build-system]
8
+ requires = ["setuptools", "cython"]
9
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,10 @@
1
+ [
2
+ {
3
+ "role": "system",
4
+ "content": "You are operating the Multi-app DAG AGILAB demo. Keep examples read-only unless the operator explicitly starts a controlled DAG run, and preserve explicit artifact handoff names when adapting the bundled template."
5
+ },
6
+ {
7
+ "role": "user",
8
+ "content": "Explain or adapt the multi-app DAG orchestration plan with explicit artifact contracts and do not imply apps have executed when only a preview was produced."
9
+ }
10
+ ]
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: agi-app-multi-dag
3
+ Version: 2026.5.30
4
+ Summary: AGILAB cross-app DAG preview showing artifact handoffs between demo projects
5
+ Author: Jean-Pierre Morard
6
+ Maintainer: Jean-Pierre Morard
7
+ License-Expression: BSD-3-Clause
8
+ Project-URL: Documentation, https://thalesgroup.github.io/agilab
9
+ Project-URL: Source, https://github.com/ThalesGroup/agilab/tree/main/src/agilab/lib/agi-app-multi-dag
10
+ Project-URL: Issues, https://github.com/ThalesGroup/agilab/issues
11
+ Project-URL: Homepage, https://github.com/ThalesGroup/agilab
12
+ Project-URL: Repository, https://github.com/ThalesGroup/agilab
13
+ Project-URL: Discussions, https://github.com/ThalesGroup/agilab/discussions
14
+ Project-URL: Changelog, https://github.com/ThalesGroup/agilab/releases
15
+ Keywords: agilab,apps,reproducibility,workflow-orchestration
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Development Status :: 4 - Beta
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Operating System :: MacOS
23
+ Classifier: Operating System :: Microsoft :: Windows
24
+ Classifier: Operating System :: POSIX :: Linux
25
+ Requires-Python: >=3.11
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: agi-core<2027.0,>=2026.05.13
29
+ Dynamic: license-file
30
+
31
+ # agi-app-multi-dag
32
+
33
+ [![PyPI version](https://img.shields.io/pypi/v/agi-app-multi-dag.svg?cacheSeconds=300)](https://pypi.org/project/agi-app-multi-dag/)
34
+ [![Python versions](https://img.shields.io/pypi/pyversions/agi-app-multi-dag.svg)](https://pypi.org/project/agi-app-multi-dag/)
35
+ [![License: BSD 3-Clause](https://img.shields.io/pypi/l/agi-app-multi-dag)](https://opensource.org/licenses/BSD-3-Clause)
36
+
37
+ `agi-app-multi-dag` publishes the `multi_app_dag_project` AGILAB app as a
38
+ self-contained PyPI payload. It is a read-only workflow-contract example rather
39
+ than a domain worker benchmark.
40
+
41
+ ## Purpose
42
+
43
+ Use this package to understand how AGILAB can connect several app projects with
44
+ explicit artifact handoffs. The bundled DAG shows a flight stage producing a
45
+ summary artifact that a weather stage can consume.
46
+
47
+ ## Installed Project
48
+
49
+ The distribution name is `agi-app-multi-dag`; the AGILAB project name is
50
+ `multi_app_dag_project`. The package exposes both `multi_app_dag` and
51
+ `multi_app_dag_project` through the `agilab.apps` entry point group, so
52
+ `AgiEnv(app="multi_app_dag_project")` resolves the project without a monorepo
53
+ checkout.
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install agi-app-multi-dag
59
+ ```
60
+
61
+ Most users get this package through `agi-apps`, `agilab[ui]`, or
62
+ `agilab[examples]`; direct installation is useful when validating one app
63
+ package in isolation.
64
+
65
+ ## Run In AGILAB
66
+
67
+ Select `multi_app_dag_project`, open `WORKFLOW`, choose the multi-app DAG template,
68
+ and inspect the runner-state preview. This package is primarily for workflow
69
+ review; use `flight_telemetry_project` or `weather_forecast_project` when you
70
+ want to execute concrete worker code.
71
+
72
+ ## Expected Inputs
73
+
74
+ The package ships a DAG template that names the producer and consumer projects.
75
+ No private data, cluster, service mode, or live external service is required for
76
+ the default preview.
77
+
78
+ ## Expected Outputs
79
+
80
+ The preview writes a runner-state JSON file under the AGILAB execution logs and
81
+ renders the runnable versus blocked stages. It does not create a domain reducer
82
+ artifact because it demonstrates cross-app orchestration, not a worker merge.
83
+
84
+ ## Change One Thing
85
+
86
+ Edit a handoff name in a copied DAG template and reload the preview. The blocked
87
+ stage should make the missing artifact contract obvious before any downstream
88
+ execution is attempted.
89
+
90
+ ## Scope
91
+
92
+ This package teaches multi-app DAG contracts. It is not a scheduler replacement,
93
+ an Airflow/Kubeflow clone, or a production workflow governance layer.
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ src/agi_app_multi_dag/__init__.py
6
+ src/agi_app_multi_dag.egg-info/PKG-INFO
7
+ src/agi_app_multi_dag.egg-info/SOURCES.txt
8
+ src/agi_app_multi_dag.egg-info/dependency_links.txt
9
+ src/agi_app_multi_dag.egg-info/entry_points.txt
10
+ src/agi_app_multi_dag.egg-info/requires.txt
11
+ src/agi_app_multi_dag.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [agilab.apps]
2
+ multi_app_dag = agi_app_multi_dag:project_root
3
+ multi_app_dag_project = agi_app_multi_dag:project_root
@@ -0,0 +1 @@
1
+ agi-core<2027.0,>=2026.05.13