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.
- agi_app_multi_dag-2026.5.30/LICENSE +22 -0
- agi_app_multi_dag-2026.5.30/PKG-INFO +93 -0
- agi_app_multi_dag-2026.5.30/README.md +63 -0
- agi_app_multi_dag-2026.5.30/pyproject.toml +94 -0
- agi_app_multi_dag-2026.5.30/setup.cfg +4 -0
- agi_app_multi_dag-2026.5.30/setup.py +47 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/__init__.py +32 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/README.md +52 -0
- 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
- 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
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/lab_stages.toml +13 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/pyproject.toml +10 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/app_args_form.py +115 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/app_settings.toml +21 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/pre_prompt.json +10 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/PKG-INFO +93 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/SOURCES.txt +11 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/dependency_links.txt +1 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/entry_points.txt +3 -0
- agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag.egg-info/requires.txt +1 -0
- 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
|
+
[](https://pypi.org/project/agi-app-multi-dag/)
|
|
34
|
+
[](https://pypi.org/project/agi-app-multi-dag/)
|
|
35
|
+
[](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
|
+
[](https://pypi.org/project/agi-app-multi-dag/)
|
|
4
|
+
[](https://pypi.org/project/agi-app-multi-dag/)
|
|
5
|
+
[](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,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
|
+
}
|
agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/lab_stages.toml
ADDED
|
@@ -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"
|
agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/pyproject.toml
ADDED
|
@@ -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"
|
agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/app_args_form.py
ADDED
|
@@ -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,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"
|
agi_app_multi_dag-2026.5.30/src/agi_app_multi_dag/project/multi_app_dag_project/src/pre_prompt.json
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/agi-app-multi-dag/)
|
|
34
|
+
[](https://pypi.org/project/agi-app-multi-dag/)
|
|
35
|
+
[](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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agi-core<2027.0,>=2026.05.13
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agi_app_multi_dag
|