Skip to content

Packaging API

Status: Generated from current Python docstrings and type hints.

Gaia package loading, compilation, environment management, and prior application. New in alpha 0: this module hosts the helpers that used to live at gaia.cli._packages, promoted out of the CLI so the engine contract is self-contained.

The error type GaiaPackagingError replaces the old GaiaCliError name — catch sites that imported the old name need both the module path and the class name updated. See Migration to alpha 0 for the full symbol-level mapping.

gaia.engine.packaging

Engine-side package loading + compilation surface.

Public facade gaia.engine.packaging: loading Gaia user packages from disk, compiling them into IR artifacts, priors application, and dependency-graph loading.

CompiledPackage dataclass

CompiledPackage(graph: LocalCanonicalGraph, knowledge_ids_by_object: dict[int, str], strategies_by_object: dict[int, Strategy], action_label_map: dict[str, str] = dict(), target_action_labels_by_id: dict[str, str] = dict(), formalization_manifest: dict[str, Any] = (lambda: {'version': 1, 'dependencies': [], 'materializations': []})(), review: ReviewManifest | None = None)

Compiled Gaia package plus runtime-object to IR-ID mappings.

to_json

to_json() -> dict[str, Any]

Serialize the compiled graph as Gaia IR JSON.

Source code in gaia/engine/lang/compiler/compile.py
132
133
134
def to_json(self) -> dict[str, Any]:
    """Serialize the compiled graph as Gaia IR JSON."""
    return self.graph.model_dump(mode="json", exclude_none=True, serialize_as_any=True)

GaiaPackagingError

Bases: RuntimeError

Engine packaging error surface (raised by load / compile / priors paths).

LoadedGaiaPackage dataclass

LoadedGaiaPackage(pkg_path: Path, config: dict[str, Any], project_config: dict[str, Any], gaia_config: dict[str, Any], project_name: str, import_name: str, source_root: Path, module: ModuleType, package: CollectedPackage)

In-memory result of load_gaia_package.

Bundles pyproject metadata, the imported user module, and the collected runtime DSL objects (Knowledge / Strategy / Operator).

DependencyGraph dataclass

DependencyGraph(import_name: str, dist_name: str, root: Path, graph: Any)

A dependency's compiled IR loaded from disk.

is_gaia_package_dir

is_gaia_package_dir(directory: str | Path) -> bool

Return True when directory is a Gaia knowledge-package root.

Source code in gaia/engine/packaging.py
73
74
75
76
77
78
79
80
81
82
83
84
def is_gaia_package_dir(directory: str | Path) -> bool:
    """Return True when *directory* is a Gaia knowledge-package root."""
    root = Path(directory)
    pyproject = root / "pyproject.toml"
    if not pyproject.exists():
        return False
    try:
        config = tomllib.loads(pyproject.read_text(encoding="utf-8"))
    except Exception:
        return False
    gaia_type = config.get("tool", {}).get("gaia", {}).get("type")
    return bool(gaia_type == "knowledge-package")

resolve_gaia_package_root

resolve_gaia_package_root(target: str | Path) -> Path | None

Resolve the nearest Gaia knowledge-package root at or above target.

Source code in gaia/engine/packaging.py
87
88
89
90
91
92
93
94
95
96
97
def resolve_gaia_package_root(target: str | Path) -> Path | None:
    """Resolve the nearest Gaia knowledge-package root at or above *target*."""
    base = Path(target).resolve()
    if not base.exists():
        return None
    if base.is_file():
        base = base.parent
    for directory in [base, *base.parents]:
        if is_gaia_package_dir(directory):
            return directory
    return None

add_editable_package_dependency

add_editable_package_dependency(local: str | Path, *, package_root: str | Path) -> Path

Add a local Gaia package as an editable dependency of another package.

Parameters:

Name Type Description Default
local str | Path

Path to the dependency package root, or a nested path inside it.

required
package_root str | Path

Path to the consuming Gaia package root, or a nested path inside that package.

required

Returns:

Type Description
Path

The resolved dependency package root.

Raises:

Type Description
GaiaPackagingError

If either side is not a Gaia package, the dependency points at the consumer itself, uv is unavailable, or uv add fails.

Source code in gaia/engine/packaging.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def add_editable_package_dependency(local: str | Path, *, package_root: str | Path) -> Path:
    """Add a local Gaia package as an editable dependency of another package.

    Args:
        local: Path to the dependency package root, or a nested path inside it.
        package_root: Path to the consuming Gaia package root, or a nested path
            inside that package.

    Returns:
        The resolved dependency package root.

    Raises:
        GaiaPackagingError: If either side is not a Gaia package, the dependency
            points at the consumer itself, ``uv`` is unavailable, or ``uv add``
            fails.
    """
    consumer_root = resolve_gaia_package_root(package_root)
    if consumer_root is None:
        raise GaiaPackagingError(
            f"target path is not inside a Gaia knowledge package: {Path(package_root).resolve()}"
        )

    local_path = Path(local)
    local_candidate = local_path if local_path.is_absolute() else consumer_root / local_path
    local_root = resolve_gaia_package_root(local_candidate)
    if local_root is None and not local_path.is_absolute():
        local_root = resolve_gaia_package_root(local_path)
    if local_root is None:
        raise GaiaPackagingError(
            f"local dependency path is not a Gaia knowledge package: {local_candidate.resolve()}"
        )
    if local_root == consumer_root:
        raise GaiaPackagingError("local dependency cannot point at the target package itself.")

    try:
        result = subprocess.run(
            ["uv", "add", "--editable", str(local_root)],
            cwd=consumer_root,
            text=True,
            capture_output=True,
        )
    except FileNotFoundError as exc:
        raise GaiaPackagingError(
            "uv is not installed. Install it with: curl -LsSf https://astral.sh/uv/install.sh | sh"
        ) from exc
    if result.returncode != 0:
        stderr = result.stderr.strip() or result.stdout.strip()
        raise GaiaPackagingError(f"uv add failed: {stderr}")

    return local_root

ensure_package_env

ensure_package_env(pkg_path: Path) -> None

Run uv sync in pkg_path so dependencies are importable.

Skipped when the directory has no pyproject.toml or when uv is not on $PATH. Failures are non-fatal (a warning is printed) because the user may manage dependencies another way.

Source code in gaia/engine/packaging.py
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
def ensure_package_env(pkg_path: Path) -> None:
    """Run ``uv sync`` in *pkg_path* so dependencies are importable.

    Skipped when the directory has no ``pyproject.toml`` or when ``uv``
    is not on ``$PATH``.  Failures are non-fatal (a warning is printed)
    because the user may manage dependencies another way.
    """
    if not (pkg_path / "pyproject.toml").exists():
        return
    import shutil

    if shutil.which("uv") is None:
        return
    result = subprocess.run(
        ["uv", "sync", "--quiet"],
        cwd=pkg_path,
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        import logging

        logging.getLogger(__name__).debug("uv sync in %s: %s", pkg_path, result.stderr.strip())

load_gaia_package

load_gaia_package(path: str | Path = '.') -> LoadedGaiaPackage

Load a Gaia knowledge package from a local directory.

Source code in gaia/engine/packaging.py
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def load_gaia_package(path: str | Path = ".") -> LoadedGaiaPackage:
    """Load a Gaia knowledge package from a local directory."""
    pkg_path = Path(path).resolve()
    pyproject = pkg_path / "pyproject.toml"
    config = _load_pyproject_config(pkg_path)
    project_config, gaia_config, project_name, version = _package_identity(config)

    import_name = project_name.removesuffix("-gaia").replace("-", "_")
    reset_inferred_package(pyproject, module_name=import_name)
    _prepend_local_dependency_source_roots(config, pkg_path)
    _prepend_pulled_package_source_roots(pkg_path)
    source_root = _source_root_for_package(pkg_path, import_name, project_name)

    source_root_str = str(source_root)
    if source_root_str not in sys.path:
        sys.path.insert(0, source_root_str)

    module = _import_package_module(import_name)
    _import_package_source_modules(import_name, source_root / import_name)

    pkg = get_inferred_package(pyproject)
    if pkg is None:
        raise GaiaPackagingError(
            "Error: no Gaia declarations found. Declare Knowledge/Strategy/Operator objects "
            "directly in the module and export the public surface via __all__ when needed."
        )

    _assign_labels_for_loaded_modules()

    _record_root_exports(module, pkg)

    module_titles = _module_titles(import_name, pkg)
    if module_titles:
        pkg._module_titles = module_titles

    pkg.name = import_name
    pkg.version = version
    if "namespace" in gaia_config:
        pkg.namespace = gaia_config["namespace"]

    return LoadedGaiaPackage(
        pkg_path=pkg_path,
        config=config,
        project_config=project_config,
        gaia_config=gaia_config,
        project_name=project_name,
        import_name=import_name,
        source_root=source_root,
        module=module,
        package=pkg,
    )

compile_loaded_package

compile_loaded_package(loaded: LoadedGaiaPackage) -> dict[str, Any]

Compile an already loaded Gaia package to IR JSON.

Source code in gaia/engine/packaging.py
595
596
597
598
599
def compile_loaded_package(loaded: LoadedGaiaPackage) -> dict[str, Any]:
    """Compile an already loaded Gaia package to IR JSON."""
    from gaia.engine.lang.compiler import compile_package

    return compile_package(loaded.package)

compile_loaded_package_artifact

compile_loaded_package_artifact(loaded: LoadedGaiaPackage) -> CompiledPackage

Compile an already loaded Gaia package to IR plus runtime mappings.

Source code in gaia/engine/packaging.py
602
603
604
605
606
607
608
609
610
611
def compile_loaded_package_artifact(loaded: LoadedGaiaPackage) -> CompiledPackage:
    """Compile an already loaded Gaia package to IR plus runtime mappings."""
    from gaia.engine.lang.compiler import compile_package_artifact
    from gaia.engine.lang.refs import ReferenceError, load_references

    try:
        references = load_references(loaded.pkg_path / "references.json")
        return compile_package_artifact(loaded.package, references=references)
    except ReferenceError as e:
        raise GaiaPackagingError(str(e)) from e

apply_package_priors

apply_package_priors(loaded: LoadedGaiaPackage) -> None

Resolve multi-source priors and inject the winning value into metadata.

Pipeline:

  1. Auto-import priors.py if present. This runs any register_prior(...) calls inside the module, populating claim.metadata['prior_records'] as a side effect. The legacy PRIORS = {...} dict is rejected with a migration error.
  2. Read the package's optional RESOLUTION_POLICY from priors.py, falling back to :func:default_resolution_policy when absent.
  3. Walk every Claim in the package. For each claim with one or more records under metadata['prior_records'], run the policy and write the winning value/justification to metadata['prior'] / metadata['prior_justification'].

All records (winner and losers) are preserved in prior_records for audit purposes and for the prior_dissent / prior_overridden diagnostics.

Authors may also call register_prior directly from __init__.py or any other module imported during package load — those calls populate prior_records before this function runs, and are resolved identically to those declared in priors.py.

Source code in gaia/engine/packaging.py
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
def apply_package_priors(loaded: LoadedGaiaPackage) -> None:
    """Resolve multi-source priors and inject the winning value into metadata.

    Pipeline:

    1. Auto-import ``priors.py`` if present. This runs any
       ``register_prior(...)`` calls inside the module, populating
       ``claim.metadata['prior_records']`` as a side effect. The legacy
       ``PRIORS = {...}`` dict is rejected with a migration error.
    2. Read the package's optional ``RESOLUTION_POLICY`` from ``priors.py``,
       falling back to :func:`default_resolution_policy` when absent.
    3. Walk every ``Claim`` in the package. For each claim with one or more
       records under ``metadata['prior_records']``, run the policy and write
       the winning value/justification to ``metadata['prior']`` /
       ``metadata['prior_justification']``.

    All records (winner and losers) are preserved in ``prior_records`` for
    audit purposes and for the ``prior_dissent`` / ``prior_overridden``
    diagnostics.

    Authors may also call ``register_prior`` directly from ``__init__.py`` or
    any other module imported during package load — those calls populate
    ``prior_records`` before this function runs, and are resolved identically
    to those declared in ``priors.py``.
    """
    policy = _load_resolution_policy(loaded)
    loaded.package._resolution_policy = policy
    try:
        resolve_priors_to_metadata(loaded.package.knowledge, policy)
    except (TypeError, ValueError) as exc:
        raise GaiaPackagingError(f"Error resolving priors: {exc}") from exc

validate_fills_relations

validate_fills_relations(loaded: LoadedGaiaPackage, compiled: CompiledPackage) -> None

Validate fills() relations without building full manifests.

Raises GaiaPackagingError if any fills() strategy has an invalid source, target, or dependency configuration. Use this for gaia build check where manifests are not needed — only validation matters.

Source code in gaia/engine/packaging.py
1213
1214
1215
1216
1217
1218
1219
1220
def validate_fills_relations(loaded: LoadedGaiaPackage, compiled: CompiledPackage) -> None:
    """Validate fills() relations without building full manifests.

    Raises GaiaPackagingError if any fills() strategy has an invalid source,
    target, or dependency configuration. Use this for ``gaia build check``
    where manifests are not needed — only validation matters.
    """
    _resolve_fills_relations(loaded, compiled)

build_package_manifests

build_package_manifests(loaded: LoadedGaiaPackage, compiled: CompiledPackage) -> dict[str, dict[str, Any]]

Build package-level interface manifests from compiled IR plus runtime package state.

Emits four sibling manifest files under .gaia/manifests/:

  • exports.json — every knowledge node in the package flagged exported. These are the package's public interface claims that downstream packages may depend on.
  • premises.json — every leaf claim (a claim with no supporting strategy in the local package) that feeds into an exported conclusion. Each entry carries a role field:

    • local_hole — the leaf claim is declared in the current package but has no derivation chain. These are the package's primary evidence and abduction alternatives — i.e. the propositions the author accepts as given inputs to the reasoning graph.
    • foreign_dependency — the leaf claim originates in an upstream *-gaia dependency and is consumed by a local strategy via the dependency's exports.json.
  • holes.json — the subset of premises.json entries whose role is local_hole. Despite the name, a "hole" here does not mean an unresolved cross-package reference (foreign dependencies already have their own resolution path via the dep's exports). A local_hole is a local leaf claim that a downstream package could optionally "fill" with more specific evidence via the fills relation — but the current package is perfectly valid with its leaves unfilled.

  • bridges.jsonfills relations declared in the local package that point at hole qids in an upstream dependency's manifest. Empty for packages with no upstream deps.

Concrete example from the watson-rfdiffusion-2023-gaia package:

  • 7 exports (the paper's exported conclusions, e.g. binder_success_rate)
  • 32 local holes: 20 primary observations (e.g. denoising_process, binder_specificity) + 12 abduction alternatives (alt_nonspecific_binding_p53_mdm2, etc.)
  • 0 foreign dependencies (watson has no upstream *-gaia deps)
  • 0 bridges (watson doesn't fill any upstream holes)

All 32 holes are declared claims in the local package — they appear in ir.json as regular knowledge nodes with exported=false and no supporting strategy. They are reported as "holes" because the holes.json manifest is indexing local leaves that downstream packages could optionally refine, not unresolved references.

See docs/specs/2026-04-08-gaia-lang-hole-fills-design.md §3.2 for the full rationale on why "hole" is a release-scoped interface role rather than a source primitive.

Source code in gaia/engine/packaging.py
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
def build_package_manifests(
    loaded: LoadedGaiaPackage, compiled: CompiledPackage
) -> dict[str, dict[str, Any]]:
    """Build package-level interface manifests from compiled IR plus runtime package state.

    Emits four sibling manifest files under ``.gaia/manifests/``:

    - ``exports.json`` — every knowledge node in the package flagged ``exported``.
      These are the package's public interface claims that downstream packages
      may depend on.
    - ``premises.json`` — every **leaf** claim (a claim with no supporting
      strategy in the local package) that feeds into an exported conclusion.
      Each entry carries a ``role`` field:

        * ``local_hole`` — the leaf claim is declared in the **current** package
          but has no derivation chain. These are the package's primary evidence
          and abduction alternatives — i.e. the propositions the author accepts
          as given inputs to the reasoning graph.
        * ``foreign_dependency`` — the leaf claim originates in an upstream
          ``*-gaia`` dependency and is consumed by a local strategy via the
          dependency's ``exports.json``.

    - ``holes.json`` — the subset of ``premises.json`` entries whose role is
      ``local_hole``. Despite the name, a "hole" here does **not** mean an
      unresolved cross-package reference (foreign dependencies already have
      their own resolution path via the dep's exports). A ``local_hole`` is a
      local leaf claim that a *downstream* package could optionally "fill" with
      more specific evidence via the ``fills`` relation — but the current
      package is perfectly valid with its leaves unfilled.
    - ``bridges.json`` — ``fills`` relations declared in the local package that
      point at hole qids in an upstream dependency's manifest. Empty for
      packages with no upstream deps.

    Concrete example from the ``watson-rfdiffusion-2023-gaia`` package:

    - 7 exports (the paper's exported conclusions, e.g. ``binder_success_rate``)
    - 32 local holes: 20 primary observations (e.g. ``denoising_process``,
      ``binder_specificity``) + 12 abduction alternatives
      (``alt_nonspecific_binding_p53_mdm2``, etc.)
    - 0 foreign dependencies (watson has no upstream ``*-gaia`` deps)
    - 0 bridges (watson doesn't fill any upstream holes)

    All 32 holes are **declared claims** in the local package — they appear in
    ``ir.json`` as regular knowledge nodes with ``exported=false`` and no
    supporting strategy. They are reported as "holes" because the `holes.json`
    manifest is indexing *local leaves that downstream packages could optionally
    refine*, not *unresolved references*.

    See ``docs/specs/2026-04-08-gaia-lang-hole-fills-design.md`` §3.2 for the
    full rationale on why "hole" is a release-scoped interface role rather than
    a source primitive.
    """
    fills_relations = _resolve_fills_relations(loaded, compiled)
    graph = compiled.graph
    knowledge_by_qid, exported_qids, exported_claim_qids = _manifest_graph_sets(graph)
    exports = _manifest_exports(graph)
    premises = _manifest_premises(
        loaded=loaded,
        compiled=compiled,
        knowledge_by_qid=knowledge_by_qid,
        exported_qids=exported_qids,
        exported_claim_qids=exported_claim_qids,
    )

    holes = [
        {key: value for key, value in premise.items() if key != "role" and key != "exported"}
        for premise in premises
        if premise["role"] == "local_hole"
    ]

    return {
        "exports.json": {
            **_manifest_base(loaded, ir_hash=graph.ir_hash or ""),
            "exports": exports,
        },
        "premises.json": {
            **_manifest_base(loaded, ir_hash=graph.ir_hash or ""),
            "premises": premises,
        },
        "holes.json": {
            **_manifest_base(loaded, ir_hash=graph.ir_hash or ""),
            "holes": holes,
        },
        "bridges.json": {
            **_manifest_base(loaded, ir_hash=graph.ir_hash or ""),
            "bridges": fills_relations,
        },
    }

collect_foreign_node_priors

collect_foreign_node_priors(graph: LocalCanonicalGraph, pkg_path: Path) -> dict[str, float]

Collect upstream beliefs for foreign knowledge nodes.

Scans .gaia/dep_beliefs/*.json for belief manifests downloaded by gaia add. For each foreign knowledge node in graph (i.e. a node whose QID does not start with the local {namespace}:{package}:: prefix), if the upstream manifest contains a matching knowledge_id, the upstream belief is included in the returned dict.

The returned dict is suitable for passing as node_priors to lower_local_graph(), which gives these values highest explicit- override priority (above metadata["prior"]).

Source code in gaia/engine/packaging.py
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
def collect_foreign_node_priors(
    graph: LocalCanonicalGraph,
    pkg_path: Path,
) -> dict[str, float]:
    """Collect upstream beliefs for foreign knowledge nodes.

    Scans ``.gaia/dep_beliefs/*.json`` for belief manifests downloaded by
    ``gaia add``.  For each foreign knowledge node in *graph* (i.e. a node
    whose QID does **not** start with the local ``{namespace}:{package}::``
    prefix), if the upstream manifest contains a matching ``knowledge_id``,
    the upstream belief is included in the returned dict.

    The returned dict is suitable for passing as ``node_priors`` to
    ``lower_local_graph()``, which gives these values highest explicit-
    override priority (above ``metadata["prior"]``).
    """
    dep_beliefs_dir = pkg_path / ".gaia" / "dep_beliefs"
    if not dep_beliefs_dir.is_dir():
        return {}

    # Build upstream beliefs mapping from all dep_beliefs files
    upstream_beliefs: dict[str, float] = {}
    for beliefs_file in sorted(dep_beliefs_dir.glob("*.json")):
        try:
            data = json.loads(beliefs_file.read_text())
        except (OSError, json.JSONDecodeError):
            continue
        beliefs_list = data.get("beliefs")
        if not isinstance(beliefs_list, list):
            continue
        for entry in beliefs_list:
            if not isinstance(entry, dict):
                continue
            kid = entry.get("knowledge_id")
            belief = entry.get("belief")
            if isinstance(kid, str) and isinstance(belief, (int, float)):
                upstream_beliefs[kid] = float(belief)

    if not upstream_beliefs:
        return {}

    # Determine local prefix to identify foreign nodes
    local_prefix = f"{graph.namespace}:{graph.package_name}::"

    foreign_priors: dict[str, float] = {}
    for knowledge in graph.knowledges:
        kid = knowledge.id
        if kid is None or kid.startswith(local_prefix):
            continue
        if kid in upstream_beliefs:
            foreign_priors[kid] = upstream_beliefs[kid]

    return foreign_priors

load_dependency_compiled_graphs

load_dependency_compiled_graphs(project_config: dict[str, Any], *, depth: int = 1, _seen: set[str] | None = None) -> list[DependencyGraph]

Discover direct -gaia dependencies and load their compiled IR.

Parameters

project_config: The [project] section of the local pyproject.toml. depth: How many levels of transitive dependencies to load. 1 = direct deps only, 2+ = recurse, -1 = unlimited. _seen: Internal dedup set (QID prefixes already loaded). Callers should not pass this.

Returns:

Flat list of :class:DependencyGraph for all discovered dependencies (deduplicated by namespace:package_name).

Source code in gaia/engine/packaging.py
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
def load_dependency_compiled_graphs(
    project_config: dict[str, Any],
    *,
    depth: int = 1,
    _seen: set[str] | None = None,
) -> list[DependencyGraph]:
    """Discover direct ``-gaia`` dependencies and load their compiled IR.

    Parameters
    ----------
    project_config:
        The ``[project]`` section of the local ``pyproject.toml``.
    depth:
        How many levels of transitive dependencies to load.
        1 = direct deps only, 2+ = recurse, -1 = unlimited.
    _seen:
        Internal dedup set (QID prefixes already loaded). Callers should
        not pass this.

    Returns:
    -------
    Flat list of :class:`DependencyGraph` for all discovered dependencies
    (deduplicated by ``namespace:package_name``).
    """
    from gaia.engine.ir.graphs import LocalCanonicalGraph

    if _seen is None:
        _seen = set()

    _specs, import_to_dist = _parse_gaia_dependencies(project_config)
    result: list[DependencyGraph] = []

    for import_name, dist_name in sorted(import_to_dist.items()):
        root = _locate_dependency_manifest_root(import_name)
        if root is None:
            raise GaiaPackagingError(
                f"Could not locate Gaia package root for dependency '{import_name}'. "
                f"Is '{dist_name}' installed?"
            )
        ir_path = root / ".gaia" / "ir.json"
        if not ir_path.exists():
            raise GaiaPackagingError(
                f"Dependency '{import_name}' is missing .gaia/ir.json. "
                f"Run 'gaia build compile' in {root}."
            )
        ir_data = _load_json_file(ir_path, description=f"{import_name} .gaia/ir.json")
        graph = LocalCanonicalGraph.model_validate(ir_data)

        # Dedup by namespace:package_name
        qid_prefix = f"{graph.namespace}:{graph.package_name}"
        if qid_prefix in _seen:
            continue
        _seen.add(qid_prefix)

        result.append(
            DependencyGraph(
                import_name=import_name,
                dist_name=dist_name,
                root=root,
                graph=graph,
            )
        )

        # Recurse into transitive deps if requested
        if depth > 1 or depth == -1:
            dep_pyproject = root / "pyproject.toml"
            if dep_pyproject.exists():
                try:
                    dep_config = tomllib.loads(dep_pyproject.read_text())
                except Exception:
                    continue
                dep_project = dep_config.get("project", {})
                next_depth = depth - 1 if depth > 1 else -1
                transitive = load_dependency_compiled_graphs(
                    dep_project, depth=next_depth, _seen=_seen
                )
                result.extend(transitive)

    return result

gaia_lang_version

gaia_lang_version() -> str

Return the installed gaia-lang version, or 'unknown' for dev checkouts.

Used by compile (to stamp .gaia/compile_metadata.json) and by tests. We deliberately return a string sentinel instead of raising so that running gaia build compile inside an un-built editable checkout still produces a valid metadata file — downstream consumers can detect 'unknown' and decide.

Source code in gaia/engine/packaging.py
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
def gaia_lang_version() -> str:
    """Return the installed gaia-lang version, or 'unknown' for dev checkouts.

    Used by compile (to stamp `.gaia/compile_metadata.json`) and by tests. We
    deliberately return a string sentinel instead of raising so that running
    `gaia build compile` inside an un-built editable checkout still produces a valid
    metadata file — downstream consumers can detect 'unknown' and decide.
    """
    try:
        return _pkg_version("gaia-lang")
    except PackageNotFoundError:
        return "unknown"

write_text_atomic

write_text_atomic(path: Path, text: str, *, encoding: str = 'utf-8') -> None

Write text via a unique temp file and atomic replace.

Source code in gaia/engine/packaging.py
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
def write_text_atomic(path: Path, text: str, *, encoding: str = "utf-8") -> None:
    """Write text via a unique temp file and atomic replace."""
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp = path.with_name(f"{path.name}.{uuid.uuid4().hex}.tmp")
    try:
        tmp.write_text(text, encoding=encoding)
        tmp.replace(path)
    finally:
        if tmp.exists():
            tmp.unlink()

write_compiled_artifacts

write_compiled_artifacts(pkg_path: Path, ir: dict[str, Any], *, manifests: dict[str, dict[str, Any]] | None = None, formalization_manifest: dict[str, Any] | None = None) -> Path

Write .gaia compilation artifacts and return the output directory.

Source code in gaia/engine/packaging.py
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
def write_compiled_artifacts(
    pkg_path: Path,
    ir: dict[str, Any],
    *,
    manifests: dict[str, dict[str, Any]] | None = None,
    formalization_manifest: dict[str, Any] | None = None,
) -> Path:
    """Write .gaia compilation artifacts and return the output directory."""
    gaia_dir = pkg_path / ".gaia"
    gaia_dir.mkdir(exist_ok=True)
    ir_json = json.dumps(ir, ensure_ascii=False, indent=2, sort_keys=True)
    write_text_atomic(gaia_dir / "ir.json", ir_json)
    write_text_atomic(gaia_dir / "ir_hash", ir["ir_hash"])
    write_text_atomic(gaia_dir / "compile_metadata.json", _render_compile_metadata(ir["ir_hash"]))
    if formalization_manifest is not None:
        write_text_atomic(
            gaia_dir / "formalization_manifest.json",
            render_manifest_json(formalization_manifest),
        )
    if manifests:
        manifests_dir = gaia_dir / "manifests"
        manifests_dir.mkdir(exist_ok=True)
        for filename, payload in manifests.items():
            write_text_atomic(manifests_dir / filename, render_manifest_json(payload))
    return gaia_dir