diff --git a/docs/for-users/language-reference.md b/docs/for-users/language-reference.md index 0e1be3eca..4720ef6a8 100644 --- a/docs/for-users/language-reference.md +++ b/docs/for-users/language-reference.md @@ -451,7 +451,7 @@ probabilistic, write down the probability model that justifies the update. | Logical hard constraints | `derive`, `compute`, `decompose`, `equal`, `contradict`, `exclusive`, formula claims | If the premises are true, the conclusion or relation should follow as a matter of logic or definition | Yes. Lowered as deterministic factors | | Hand-written probabilistic soft constraints | `infer`, `associate` | You are directly committing to conditional probabilities or an association strength | Yes. Lowered as probabilistic factors | | Model-based Bayesian soft constraints | `bayes.model`, `bayes.compare` | The probabilities come from a statistical model, distribution, and observed data | Yes. Lowered to likelihood-style probabilistic factors and reviewable relations | -| Composed workflows | `@compose` | A Python function groups several actions into one named workflow | The child actions enter inference according to their own kinds | +| Composed workflows | `@composition` | A Python function groups several actions into one named workflow | The child actions enter inference according to their own kinds | This split exists for a practical reason: reviewers and downstream users need to know what kind of commitment each line makes. A scaffold note says "work @@ -944,7 +944,7 @@ from gaia.engine.lang.compat import ( | `support([P], C, prior=...)` | `derive(C, given=[P])` (deterministic) or `infer(P, hypothesis=C, p_e_given_h=..., p_e_given_not_h=...)` / `bayes.compare(...)` (probabilistic) | `P` is the evidence; `C` is the hypothesis whose belief gets updated. | | `deduction([P], C)` | `derive(C, given=[P])` | Strict logical entailment lowers to a hard conditional implication. | | `compare / abduction / induction / analogy / extrapolation / elimination / case_analysis / mathematical_induction` | Author the deterministic skeleton with `derive(...)` + relation verbs, or `bayes.compare(...)` for explicit Bayesian comparisons | Do not use these as shortcuts for uncertainty that hasn't been spelled out as claims. | -| `composite(..., sub_strategies=[...])` | Plain `derive(...)` chains plus `@compose` for the workflow boundary | Sub-strategy priors are no longer the v0.5 surface for soft leaves; use `infer(...)` / `bayes.compare(...)` instead. | +| `composite(..., sub_strategies=[...])` | Plain `derive(...)` chains plus `@composition` for the workflow boundary | Sub-strategy priors are no longer the v0.5 surface for soft leaves; use `infer(...)` / `bayes.compare(...)` instead. | | `infer([premises], conclusion, ...)` (legacy positional) | `infer(evidence, hypothesis=..., given=..., p_e_given_h=..., p_e_given_not_h=...)` | The legacy positional form is preserved as a deprecated path; the keyword form is the v0.5 contract. | | `contradiction(a, b)` / `equivalence(a, b)` / `complement(a, b)` | `contradict(a, b)` / `equal(a, b)` / `exclusive(a, b)` | The relation-verb forms appear in review manifests and emit reviewable warrant helpers. | | `disjunction(*claims)` / `and_(...)` / `or_(...)` / `not_(...)` | `claim(..., formula=lor(...))` / `claim(..., formula=land(...))` / `claim(..., formula=lnot(...))`, or the dunder formula sugar `a \| b`, `a & b`, `~a` | Formula AST is the v0.5 way to express structural Boolean expressions; dunders now return Formula nodes, not legacy helper claims. | diff --git a/docs/foundations/gaia-lang/knowledge-and-reasoning.md b/docs/foundations/gaia-lang/knowledge-and-reasoning.md index bd7c2c503..f01ac8040 100644 --- a/docs/foundations/gaia-lang/knowledge-and-reasoning.md +++ b/docs/foundations/gaia-lang/knowledge-and-reasoning.md @@ -192,7 +192,7 @@ Scaffold actions compile only into `.gaia/formalization_manifest.json`. They are ### 3.5 Compose — action-level composition -`@compose(name=..., version=..., warrants=None, ...)` decorates a Python function as a named action workflow. Inside the function the author calls other action verbs; calling the decorated function: +`@composition(name=..., version=..., warrants=None, ...)` (deprecated alias: `@compose`) decorates a Python function as a named action workflow. Inside the function the author calls other action verbs; calling the decorated function: 1. Captures every action emitted inside the call into a `Compose` runtime object. 2. Records `inputs` (Knowledge values passed as args, plus claims captured from background and child actions), `actions` (the ordered list of children, by reference or by id), and `conclusion` (the function's return value, which **must** be a `Claim`). @@ -452,7 +452,7 @@ Operators that emit a relation-result conclusion (`equivalence`, `contradiction` | `Infer` / `Associate` action | `Strategy` with explicit CPT + warrant helper `Knowledge` | warrant helper is the primary label resolution target | | `Equal` / `Contradict` / `Exclusive` action | `Operator` + helper `Knowledge` | top-level operator gets `lco_*` ID; helper is reviewable | | `Decompose` action | formula `Operator`s over `parts` + `Operator(equivalence, [whole, formula_helper])` | enforces atomic-parts match and acyclicity | -| `@compose`-decorated function | `gaia.engine.ir.Compose` first-class IR node | structure-hashed over inputs, child actions, conclusion, warrants | +| `@composition`-decorated function | `gaia.engine.ir.Compose` first-class IR node | structure-hashed over inputs, child actions, conclusion, warrants | | `DependsOn` action | (not lowered) | authoring metadata only | Identity assignment: diff --git a/docs/reference/cli/author.md b/docs/reference/cli/author.md index 8ed5fc44c..2fc816b84 100644 --- a/docs/reference/cli/author.md +++ b/docs/reference/cli/author.md @@ -77,8 +77,8 @@ records its metadata in `pyproject.toml`). | Scaffold | `depends-on` | `depends_on(conclusion, given, *, rationale="", background=None, label=None)` | yes | | Scaffold | `candidate-relation` | `candidate_relation(*, claims, pattern, rationale="", background=None, label=None)` | yes | | Scaffold | `materialize` | `materialize(scaffold, *, by, rationale="", label=None)` | yes | -| Composition | `compose` | `@compose(name=…, version=…)` decorating `def fn(...) -> Claim` | **no — file-based** | -| Composition | `composition` | alias of `compose` | **no — file-based** | +| Composition | `composition` | `@composition(name=…, version=…)` decorating `def fn(...) -> Claim` | **no — file-based** | +| Composition | `compose` | deprecated alias of `composition` | **no — file-based** | | Typed terms | `variable` | `Variable(symbol=…, domain=…, value=…)` or `Constant(value, primitive)` | yes | The 21st verb in this reference, `gaia pkg scaffold`, lives in the `pkg` @@ -283,7 +283,7 @@ gaia author compute --conclusion-type --label [--target ] | `--given ` | no | Comma-separated input identifiers. | The decorator form `@compute` stays at Python-source level (same logic as -`compose` / `composition`). +`composition` / its deprecated alias `compose`). ### `infer` @@ -403,16 +403,19 @@ gaia author materialize --scaffold --by --label \ | `--scaffold ` | yes | Scaffold identifier the materialization targets. | | `--by ` | yes | Comma-separated identifiers used as the `by=` arg (single-element scalars and multi-element lists both render as `by=[...]`). | -## File-based verbs — `compose` / `composition` +## File-based verbs — `composition` (+ deprecated alias `compose`) -The two composition verbs do **not** append a statement to +`composition` is the canonical verb; `compose` is a deprecated alias kept +for compatibility (it reads as the inverse of `decompose`, which it is +not — see issue #759) and surfaces a deprecation warning in the envelope. +The composition verbs do **not** append a statement to `__init__.py`. The composition primitive is a Python-decorator-level concept (its body is an arbitrary Python function capturing nested `Action` invocations through a ContextVar), so the cli takes the file containing the decorated function as input and registers its metadata. ``` -gaia author --from-file [--target ] +gaia author --from-file [--target ] [--check/--no-check] [--human] [--interactive] [--json/--no-json] ``` @@ -425,7 +428,7 @@ gaia author --from-file [--target ] diagnostic): * `--from-file` must exist and parse as valid Python. -* Exactly **one** `@compose` / `@composition`-decorated `FunctionDef` per +* Exactly **one** `@composition` / `@compose`-decorated `FunctionDef` per file (the *one-compose-per-file rule*). Both bare names and `.compose` Attribute-shaped references are counted. * The decorator must carry `name=` and `version=` string kwargs. @@ -688,9 +691,10 @@ from "your Python parses but the sandbox does not allow it". ## Compose validate-and-register -The `compose` / `composition` verbs (see [File-based verbs](#file-based- -verbs-compose-composition)) extract metadata from a Python file with a -single `@compose` / `@composition`-decorated `FunctionDef` and record +The `composition` verb and its deprecated `compose` alias (see +[File-based verbs](#file-based-verbs-composition-deprecated-alias-compose)) +extract metadata from a Python file with a +single `@composition` / `@compose`-decorated `FunctionDef` and record that metadata in the target package's `pyproject.toml` as a `[[tool.gaia.compositions]]` array-of-tables entry. Five string fields: `name` / `version` / `file` / `function` / `registered_at` @@ -704,9 +708,9 @@ file contains zero or more than one decorated function, the verb exits The minimal pattern in the file: ```python -from gaia.engine.lang import compose +from gaia.engine.lang import composition -@compose(name="my_strategy", version="0.1.0") +@composition(name="my_strategy", version="0.1.0") def my_strategy(...) -> Claim: ... ``` diff --git a/gaia/_skills/gaia-formalize-fine/SKILL.md b/gaia/_skills/gaia-formalize-fine/SKILL.md index 7fb5e1e3b..f8a5190a1 100644 --- a/gaia/_skills/gaia-formalize-fine/SKILL.md +++ b/gaia/_skills/gaia-formalize-fine/SKILL.md @@ -52,7 +52,7 @@ gaia run infer . → .gaia/beliefs.json | 1 | Content extraction | Are claims and notes extracted? Atomic? | | 2 | Reasoning connections | Are derivations, inferences, observations, computes, and structural relations modelled? | | 3 | Content completeness | Any missing premises, orphans, or `@label` errors? | -| 4 | Strategy precision | Is each author verb the right one (`derive` vs `infer` vs `compute` vs `observe` vs `compose`)? | +| 4 | Strategy precision | Is each author verb the right one (`derive` vs `infer` vs `compute` vs `observe` vs `composition`)? | | 5 | Structural integrity | Is evidence independent? Are `contradict` / `exclusive` semantics correct? | | 6 | Standalone readability | Can a reviewer understand everything without the source? | @@ -84,7 +84,7 @@ The methodology below leans on this fixed set of CLI calls. Drill into `gaia ` — run BP, emit `.gaia/beliefs.json`. Pass `--depth N` (>0) to merge dependency packages' factor graphs for joint cross-package inference. - `gaia run render --target github` — generate `.github-output/` README + narrative outline (handoff to `../gaia-publish/SKILL.md`). diff --git a/gaia/_skills/gaia-formalize-fine/references/pass-2-connect.md b/gaia/_skills/gaia-formalize-fine/references/pass-2-connect.md index e341187e0..f3b3d10db 100644 --- a/gaia/_skills/gaia-formalize-fine/references/pass-2-connect.md +++ b/gaia/_skills/gaia-formalize-fine/references/pass-2-connect.md @@ -13,7 +13,7 @@ For each claim "supported by other claims," choose one of these author verbs: - `gaia author observe --conclusion C [--value ... --error ...]` — raw measurement: ties a Claim, Variable, or Distribution to an observed value. Use for experimental measurements that anchor the graph in data. - `gaia author compute --conclusion-type T --fn f --given P1,P2,...` — deterministic mapping: a named callable produces the result from the premises. Use when the source presents a closed-form computation whose function is captured by code. - `gaia author decompose --whole W --parts A,B,... --formula-template and|or|atom` — structural split: composite claim → atomic parts. Use when an aggregate claim is best read as a conjunction (or disjunction) of independently judgeable atoms. -- `gaia author compose --from-file pattern.py` — register a reusable multi-step pattern as a `@compose`-decorated function. Use Pass 4 to refine flat `derive`/`infer` calls into compositions when meaningful intermediate propositions appear. +- `gaia author composition --from-file pattern.py` — register a reusable multi-step pattern as a `@composition`-decorated function. Use Pass 4 to refine flat `derive`/`infer` calls into compositions when meaningful intermediate propositions appear. Plus the structural-relation verbs (no `--given`; these state a logical constraint between claims): @@ -119,6 +119,6 @@ Contradictions and exclusives are especially valuable in BP because they create Before moving to Pass 3, verify: - **Theory-experiment pairs use `infer`?** Every place the source compares a theoretical prediction against an experimental observation should be connected via `infer(evidence=obs, hypothesis=pred, --p-e-given-h ..., --p-e-given-not-h ...)`. The relationship is explanatory ("does the observation support the prediction?"), not a rigid step-by-step derivation. -- **Multiple observations confirming one law?** If several independent observations all support the same general rule, the conclusion claim (the law) should be a `derive(...)` over those observations — and in Pass 4 you will likely refactor that to a `compose`'d pattern that names the generalisation step explicitly. +- **Multiple observations confirming one law?** If several independent observations all support the same general rule, the conclusion claim (the law) should be a `derive(...)` over those observations — and in Pass 4 you will likely refactor that to a `composition` that names the generalisation step explicitly. - **No missing alternatives?** When the source compares competing hypotheses against one observation, every alternative should be extracted as a claim and either chained as additional `infer` evidence or wired with `exclusive` if the source treats the alternatives as exhaustive. - **Contradictions modelled?** Every contradictory claim pair identified in Pass 1 should now have a `contradict(...)` (or `exclusive(...)`) operator. Also check: did any new contradictions emerge while writing relations? diff --git a/gaia/_skills/gaia-formalize-fine/references/pass-4-strategy-types.md b/gaia/_skills/gaia-formalize-fine/references/pass-4-strategy-types.md index 6bfeb038f..acf7c2639 100644 --- a/gaia/_skills/gaia-formalize-fine/references/pass-4-strategy-types.md +++ b/gaia/_skills/gaia-formalize-fine/references/pass-4-strategy-types.md @@ -14,7 +14,7 @@ Passes 2-3 produce a graph dominated by `infer` (the general fall-back). Pass 4 | `infer` | Bayesian update: explicit P(E\|H), optional P(E\|~H) | Theory-vs-experiment fit, single-evidence updates to a hypothesis | `--p-e-given-h` (required), `--p-e-given-not-h` (defaults 0.5) | | `compute` | Deterministic mapping: callable `fn` produces conclusion from premises | Closed-form computations where the function is in code | `--fn` identifier of a callable; conclusion is the function's output Claim | | `observe` | Measurement event tying Claim / Variable / Distribution to data | Experimental observations that anchor the graph | `--value` / `--error` for quantity form, or discrete observation against a premise list | -| `compose` | Reusable multi-step pattern: `@compose`-decorated function | Recurring derivation patterns that need a named, registered shape | Author the `pattern.py` and register via `gaia author compose --from-file` | +| `composition` | Reusable multi-step pattern: `@composition`-decorated function | Recurring derivation patterns that need a named, registered shape | Author the `pattern.py` and register via `gaia author composition --from-file` | | `decompose` | Structural split: composite → atomic parts via `and`/`or`/`atom` | Aggregate claim is best read as the conjunction of independently judgeable parts | `--formula-template` or `--formula-expr` | Also available as **structural verbs** (modelled in Pass 2 alongside the rest, not in Pass 4): @@ -49,7 +49,7 @@ For each `infer` relation drafted in Pass 2: NO ↓ Does the same multi-step pattern recur across multiple conclusions, and is naming intermediate propositions worthwhile? - YES → compose (extract into a @compose pattern; per-call the wrapper authors a derive over + YES → composition (extract into a @composition pattern; per-call the wrapper authors a derive over the composition's intermediate Claims) NO → keep infer (with the most informative likelihood you can justify) ``` @@ -64,7 +64,7 @@ The release/0.4 SKILL talked about several named reasoning patterns. Several hav **Theory-experiment comparison ("abduction")**: extract the theoretical prediction and the experimental observation as separate claims (Pass 1), then use `infer(evidence=obs, hypothesis=pred, --p-e-given-h ...)`. When several alternative theories compete, chain `infer` against each candidate hypothesis with its own likelihoods. When the alternatives are mutually exclusive in the paper's framing, add `exclusive(a, b)` for the two-alternative case or `decompose --formula-template or` for three or more (`exclusive` is strictly binary). The abduction *concept* — the prior on the alternative reflects explanatory power for the specific observation, not the alternative's truth in general — survives intact; that deep guide lives in `../../gaia-review/SKILL.md`. -**Repeated-observation generalisation ("induction")**: there is no single v0.5 verb. The recommended idiom is `derive` over a `compose`'d generalisation step. Specifically: author each observation as its own claim, then either (a) for the simple flat case, `derive(law, given=[obs_a, obs_b, obs_c], rationale=...)`, or (b) when the generalisation involves a named pattern, define a `@compose` function that takes the observations and the law and returns the law's `derive` step, and register it via `gaia author compose --from-file`. The underlying judgement — each observation must be **independent**; if dependent, extract the shared dependency as an explicit claim in Pass 5 — still applies. +**Repeated-observation generalisation ("induction")**: there is no single v0.5 verb. The recommended idiom is `derive` over a `compose`'d generalisation step. Specifically: author each observation as its own claim, then either (a) for the simple flat case, `derive(law, given=[obs_a, obs_b, obs_c], rationale=...)`, or (b) when the generalisation involves a named pattern, define a `@composition` function that takes the observations and the law and returns the law's `derive` step, and register it via `gaia author composition --from-file`. The underlying judgement — each observation must be **independent**; if dependent, extract the shared dependency as an explicit claim in Pass 5 — still applies. **Process of elimination, proof by cases, mathematical induction, cross-system analogy, extrapolation beyond measured range:** these patterns **have no single-verb v0.5 form**. The recommended idiom for each: @@ -89,8 +89,8 @@ Every Claim **must** be assigned to a named variable (no `_` prefix for claims t `compose` is the v0.5 way to capture **complex reasoning with meaningful intermediate steps**. Two triggers: -1. **3+ premises and no `decompose` fit.** A flat `derive` over 4+ premises suffers the BP multiplicative effect — small uncertainties on each premise compound on the conclusion. If you can name meaningful intermediate propositions (not stubs introduced purely to split the call), refactor into a `@compose` pattern whose intermediate Claims are independently judgeable. -2. **Recurring pattern.** The same shape of derivation appears across multiple conclusions. Register it once via `gaia author compose --from-file` and reuse. +1. **3+ premises and no `decompose` fit.** A flat `derive` over 4+ premises suffers the BP multiplicative effect — small uncertainties on each premise compound on the conclusion. If you can name meaningful intermediate propositions (not stubs introduced purely to split the call), refactor into a `@composition` pattern whose intermediate Claims are independently judgeable. +2. **Recurring pattern.** The same shape of derivation appears across multiple conclusions. Register it once via `gaia author composition --from-file` and reuse. If decomposition would be forced — no meaningful intermediate proposition exists — 3 premises is acceptable to keep as `derive` or `infer`; 4+ premises must decompose, otherwise BP multiplicative effect will severely suppress belief. diff --git a/gaia/cli/commands/author/__init__.py b/gaia/cli/commands/author/__init__.py index 05bf12a72..043217d8f 100644 --- a/gaia/cli/commands/author/__init__.py +++ b/gaia/cli/commands/author/__init__.py @@ -6,7 +6,8 @@ ``contradict`` / ``exclusive`` / ``decompose`` / ``observe`` / ``compute`` / ``infer`` / ``associate`` / ``parameter`` / ``register_prior`` / ``depends_on`` / ``candidate_relation`` / -``materialize`` / ``variable`` / ``compose`` / ``composition``). Direct +``materialize`` / ``variable`` / ``composition`` (+ its deprecated alias +``compose``)). Direct SDK authoring (``gaia sdk`` + writing the DSL in Python) is the primary path; this CLI is an OPTIONAL convenience for humans and agents alike. When used, it owns identifier collision checks, reference resolution, @@ -23,9 +24,9 @@ ``question`` / ``contradict`` / ``exclusive`` / ``decompose`` / ``observe`` / ``compute`` / ``infer`` / ``associate`` / ``parameter`` / ``register_prior`` / ``depends_on`` / ``candidate_relation`` / -``materialize`` / ``variable``) plus the two file-based ``compose`` / -``composition`` -validate-and-register verbs (see :mod:`.compose`). Prose-mode +``materialize`` / ``variable``) plus the file-based ``composition`` +validate-and-register verb and its deprecated ``compose`` alias +(see :mod:`.compose`). Prose-mode ``---content`` flags, two pre-write warning kinds, and a restricted-globals formula sandbox round out the authoring surface; the engine's deprecation catalog is discovered via an AST scan over the DSL diff --git a/gaia/cli/commands/author/compose.py b/gaia/cli/commands/author/compose.py index 3979b21cc..f77995929 100644 --- a/gaia/cli/commands/author/compose.py +++ b/gaia/cli/commands/author/compose.py @@ -1,4 +1,4 @@ -"""``gaia author compose`` / ``gaia author composition`` — validate + register. +"""``gaia author composition`` (+ deprecated alias ``compose``) — validate + register. The composition primitive is a Python-decorator-level concept (its body is an arbitrary Python function capturing nested ``Action`` invocations @@ -11,14 +11,14 @@ tooling can discover compositions without importing the package. The author inventory totals 19 verbs: 17 statement-emitting plus the -two file-based ``compose`` / ``composition`` validate-and-register -verbs documented here. +file-based ``composition`` validate-and-register verb and its +deprecated ``compose`` alias, both documented here. CLI surface:: - gaia author compose --from-file [--target ] - [--check / --no-check] [--human] - [--interactive] [--json/--no-json] + gaia author composition --from-file [--target ] + [--check / --no-check] [--human] + [--interactive] [--json/--no-json] Validation contract (each failure exits 2 with a structured diagnostic): @@ -37,8 +37,10 @@ We **insert-or-update by name**: re-running ``compose`` for the same ``name`` overwrites the entry (idempotent). -The ``composition`` alias verb in :mod:`._init_` reuses this impl with -``verb="composition"`` to keep the cli-surface inventory symmetric. +``composition`` is the canonical verb; ``compose`` is a deprecated alias +kept for compatibility (it reads as the inverse of ``decompose``, which it +is not — see issue #759). Both reuse this impl, distinguished by ``verb=``; +the ``compose`` spelling surfaces a deprecation warning in the envelope. ``--check`` (default on) drives a real :func:`postwrite_check` against the target package after registration succeeds. Semantics: @@ -91,14 +93,14 @@ _EPILOG_EXAMPLE = """ Invoke: - $ gaia author compose --from-file pattern.py --target ./my-pkg $ gaia author composition --from-file pattern.py --target ./my-pkg + $ gaia author compose --from-file pattern.py --target ./my-pkg (deprecated alias) Example pattern file (`pattern.py`): - from gaia.engine.lang import compose, claim, derive + from gaia.engine.lang import composition, claim, derive - @compose(name="my-pkg:my-pattern", version="1.0") + @composition(name="my-pkg:my-pattern", version="1.0") def my_pattern(input_claim: Claim) -> Claim: result = derive(input_claim, given=[input_claim], label="warranted") return result @@ -579,6 +581,11 @@ def _run_compose( outcome; the envelope distinguishes "registered and verified" from "registered but failed validation". """ + deprecation_warnings = ( + ["'gaia author compose' is deprecated; use 'gaia author composition'"] + if verb == "compose" + else [] + ) target_root = Path(target).resolve() file_path = Path(from_file).resolve() @@ -674,11 +681,11 @@ def _run_compose( # itself compiles cleanly. The two are decoupled by design. if not check: payload["check"] = "skipped" - _emit_success(verb, payload=payload, warnings_list=[], human=human) + _emit_success(verb, payload=payload, warnings_list=deprecation_warnings, human=human) return post = postwrite_check(target_root) - warnings_messages = [w.message for w in post.warnings] + warnings_messages = [*deprecation_warnings, *(w.message for w in post.warnings)] if not post.ok: _emit_postwrite_failure( verb, @@ -728,13 +735,13 @@ def compose_command( interactive: bool = typer.Option( False, "--interactive", - help="Reserved for symmetry; compose does not currently surface warnings.", + help=("Reserved for symmetry; the deprecation notice is envelope-only and never prompts."), ), json_: bool = typer.Option( True, "--json/--no-json", help="JSON-first output (default; redundant for clarity)." ), ) -> None: - r"""Validate + register a @compose-decorated function into pkg metadata.""" + r"""Deprecated alias of ``composition``; validate + register a pattern file.""" del json_, interactive _run_compose("compose", from_file=from_file, target=target, human=human, check=check) @@ -776,7 +783,7 @@ def composition_command( True, "--json/--no-json", help="JSON-first output (default; redundant for clarity)." ), ) -> None: - r"""Alias of ``compose``; validate + register a @composition-decorated function.""" + r"""Validate + register a @composition-decorated function into pkg metadata.""" del json_, interactive _run_compose("composition", from_file=from_file, target=target, human=human, check=check) diff --git a/gaia/cli/commands/author/list.py b/gaia/cli/commands/author/list.py index e14a1a60c..dfd26b5a4 100644 --- a/gaia/cli/commands/author/list.py +++ b/gaia/cli/commands/author/list.py @@ -63,9 +63,9 @@ # module scope). The list mirrors the 17 statement-emitting verbs plus the # typed-term factories ``Variable`` / ``Constant``. Hyphenated cli verbs map # to underscored callables in source (``depends-on`` → ``depends_on``); the -# output ``kind`` always uses the underscored callable form. ``compose`` / -# ``composition`` are NOT in this set — they live in pyproject.toml and are -# handled by the trailing compositions section. +# output ``kind`` always uses the underscored callable form. ``composition`` +# (and its deprecated ``compose`` alias) is NOT in this set — compositions +# live in pyproject.toml and are handled by the trailing compositions section. _AUTHOR_CALLABLES: frozenset[str] = frozenset( { "claim", @@ -543,9 +543,9 @@ def _read_compositions(pyproject: Path) -> list[dict[str, Any]]: """Read ``[[tool.gaia.compositions]]`` entries from pyproject.toml. Returns a list of dicts shaped - ``{"name": str, "kind": "compose", "target": str, "version": str}``. - The ``kind`` field is always ``"compose"`` (compose/composition are - aliases on the cli surface that share the same registration table). + ``{"name": str, "kind": "composition", "target": str, "version": str}``. + The ``kind`` field is always ``"composition"`` (the deprecated + ``compose`` alias shares the same registration table). ``target`` is the function reference recorded at registration time (e.g. ``"galileo_v05_compositions.galileo_v05"``); we render it as ``.`` if both are present, else the file path. @@ -580,7 +580,7 @@ def _read_compositions(pyproject: Path) -> list[dict[str, Any]]: out.append( { "name": name, - "kind": "compose", + "kind": "composition", "target": target_repr, "version": version if isinstance(version, str) else "", } @@ -653,7 +653,7 @@ def _render_human( lines.append("Compositions registered in pyproject.toml:") for entry in compositions: name = entry.get("name", "") - kind = entry.get("kind", "compose") + kind = entry.get("kind", "composition") target = entry.get("target", "") lines.append(f" {name:<20} ({kind:<12}) {target}") elif total == 0 and not warnings_list: diff --git a/gaia/cli/commands/sdk/_cheatsheet.py b/gaia/cli/commands/sdk/_cheatsheet.py index 1eaa574a6..af80e0501 100644 --- a/gaia/cli/commands/sdk/_cheatsheet.py +++ b/gaia/cli/commands/sdk/_cheatsheet.py @@ -54,7 +54,7 @@ depends_on, candidate_relation, materialize, # Scaffold annotations observe, derive, compute, decompose, # Reasoning actions — hard infer, # Reasoning actions — soft - compose, composition, # Action composition + composition, # Action composition register_prior, # External prior records Normal, LogNormal, Beta, Gamma, StudentT, # Distribution factories Cauchy, ChiSquared, Binomial, BetaBinomial, Poisson, @@ -159,10 +159,11 @@ class ResultClaim(Claim): ## Action composition ```python -@composition -def my_argument(): +@composition(name="my-pkg:my-argument", version="1.0") +def my_argument(...) -> Claim: ... # group several actions as one unit -compose(action_a, action_b) # combine actions explicitly +# `compose` is a deprecated alias of `composition` (it is NOT the inverse +# of `decompose`). ``` ## Typed terms + formulas diff --git a/gaia/cli/main.py b/gaia/cli/main.py index 81740e557..c9ea84086 100644 --- a/gaia/cli/main.py +++ b/gaia/cli/main.py @@ -15,7 +15,7 @@ note / question / contradict / exclusive / decompose / observe / compute / infer / associate / parameter / register-prior / variable / depends-on / candidate-relation / materialize / - compose / composition + composition (+ deprecated alias compose) bayes model / compare / distribution literals example galileo / mendel (print or save the cli walkthrough for a shipping v0.5 example package) @@ -290,9 +290,9 @@ def _review_skeleton(ctx: typer.Context) -> None: # checked appends, but not the recommended first move. Every write is # confined to the package's composed ``authored/`` submodule; the CLI # never writes the package-root ``__init__.py``. 18 statement-emitting -# verbs share the same pre-write + envelope skeleton; ``compose`` / -# ``composition`` use a file-based validate-and-register surface (see -# gaia.cli.commands.author.compose). +# verbs share the same pre-write + envelope skeleton; ``composition`` +# (+ its deprecated ``compose`` alias) uses a file-based +# validate-and-register surface (see gaia.cli.commands.author.compose). author_app = typer.Typer( name="author", @@ -302,7 +302,7 @@ def _review_skeleton(ctx: typer.Context) -> None: "claim / artifact / figure / equal / derive / note / question / " "contradict / exclusive / decompose / observe / compute / infer / " "associate / parameter / register-prior / variable / depends-on / " - "candidate-relation / materialize / compose / composition / list." + "candidate-relation / materialize / composition / list." ), no_args_is_help=True, ) diff --git a/gaia/engine/lang/runtime/composition.py b/gaia/engine/lang/runtime/composition.py index 92819f526..dc71c4f6e 100644 --- a/gaia/engine/lang/runtime/composition.py +++ b/gaia/engine/lang/runtime/composition.py @@ -2,6 +2,7 @@ from __future__ import annotations +import warnings from collections.abc import Callable, Iterable from contextvars import ContextVar from dataclasses import dataclass, field @@ -150,7 +151,7 @@ def _infer_inputs( return _unique_knowledge([*explicit_inputs, *action_inputs]) -def compose( +def composition( *, name: str, version: str, @@ -171,7 +172,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Claim: finally: _current_composition_scope.reset(token) if not isinstance(result, Claim): - raise TypeError("@compose functions must return a Claim object") + raise TypeError("@composition functions must return a Claim object") compose_background = list(background or []) compose_action = Compose( @@ -199,5 +200,35 @@ def wrapper(*args: Any, **kwargs: Any) -> Claim: return decorator -composition = compose +def compose( + *, + name: str, + version: str, + background: list[Knowledge] | None = None, + warrants: list[Claim] | None = None, + rationale: str = "", + label: str | None = None, +) -> Callable[[Callable[..., Claim]], Callable[..., Claim]]: + """Deprecated alias of :func:`composition`. + + The verb form ``compose`` reads as the inverse of ``decompose``, which it + is not — ``decompose`` is a claim-level structural equivalence while this + decorator builds an action-level composition template. The noun + ``composition`` is the canonical name (see issue #759). + """ + warnings.warn( + "compose() is deprecated; use composition()", + DeprecationWarning, + stacklevel=2, + ) + return composition( + name=name, + version=version, + background=background, + warrants=warrants, + rationale=rationale, + label=label, + ) + + Composition = Compose diff --git a/tests/baseline/__snapshots__/test_help_snapshots/test_help_text_snapshot[gaia_help].json b/tests/baseline/__snapshots__/test_help_snapshots/test_help_text_snapshot[gaia_help].json index 3eba1aea3..51d4f9a92 100644 --- a/tests/baseline/__snapshots__/test_help_snapshots/test_help_text_snapshot[gaia_help].json +++ b/tests/baseline/__snapshots__/test_help_snapshots/test_help_text_snapshot[gaia_help].json @@ -4,5 +4,5 @@ ], "exit_code": 0, "stderr": "", - "stdout": "\n Usage: gaia [OPTIONS] COMMAND [ARGS]...\n\n Gaia — knowledge package authoring toolkit.\n\n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --version Show version, channel, commit, and ir_schema; │\n│ then exit. │\n│ --install-completion Install completion for the current shell. │\n│ --show-completion Show completion for the current shell, to copy │\n│ it or customize the installation. │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ sdk Generate the Gaia SDK reference + cheat sheet (start here). │\n│ build Build artifacts (init / compile / check). │\n│ run Run inference and rendering (infer / render). │\n│ inspect Inspect compiled artifacts (starmap). │\n│ review Reviewer tooling (alpha 0: skeleton only — no commands yet). │\n│ inquiry Gaia Inquiry — semantic review loop (focus / review / reject / │\n│ obligation / hypothesis / tactics). │\n│ pkg Package operations (add / add-import / add-module / register / │\n│ scaffold). │\n│ author Optional authoring convenience (primary path: `gaia sdk` + write │\n│ the DSL directly). Writes into the package's authored/ submodule. │\n│ Verbs: claim / artifact / figure / equal / derive / note / question │\n│ / contradict / exclusive / decompose / observe / compute / infer / │\n│ associate / parameter / register-prior / variable / depends-on / │\n│ candidate-relation / materialize / compose / composition / list. │\n│ bayes Bayesian-modelling authoring (model / compare / Binomial / │\n│ BetaBinomial / Normal / LogNormal / Beta / Exponential / Gamma / │\n│ StudentT / Cauchy / ChiSquared / Poisson). │\n│ example Print or save the cli walkthrough script for a shipping example │\n│ package (galileo / mendel). │\n│ skill Materialise bundled Gaia skills into cwd (register / list). │\n│ search Retrieval backends (lkm). │\n│ trace Gaia ARM Trace — verify and review execution traces (verify / │\n│ review / show). │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n What gaia does:\n Gaia turns a structured argument — claims, observations, derivations — into a\n compiled Bayesian network. Start with `gaia sdk` to get the SDK reference +\n cheat sheet, then author the DSL directly in Python (the primary path; `gaia\n author …` is an optional convenience). Compile with `gaia build compile`, run\n inference with `gaia run infer`, and render a slide-deck-ready artifact with\n `gaia run render`. See `gaia example galileo` for an end-to-end walkthrough.\n What is a knowledge package?\n A knowledge package is a small Python project that captures a structured\n argument — premises, derivations, observations — that gaia compiles into a\n Bayesian network. The shipped examples (`gaia example galileo`, `gaia example\n mendel`) walk through building one end-to-end.\n Typical authoring flow:\n $ gaia build init my-pkg-gaia\n $ gaia sdk # SDK reference + CHEATSHEET.md; author directly\n $ gaia build compile ./my-pkg-gaia\n $ gaia run infer ./my-pkg-gaia\n Run `gaia --help` for per-group verb references.\n Run `gaia example --help` to see how a full demo is authored.\n\n\n" + "stdout": "\n Usage: gaia [OPTIONS] COMMAND [ARGS]...\n\n Gaia — knowledge package authoring toolkit.\n\n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --version Show version, channel, commit, and ir_schema; │\n│ then exit. │\n│ --install-completion Install completion for the current shell. │\n│ --show-completion Show completion for the current shell, to copy │\n│ it or customize the installation. │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ sdk Generate the Gaia SDK reference + cheat sheet (start here). │\n│ build Build artifacts (init / compile / check). │\n│ run Run inference and rendering (infer / render). │\n│ inspect Inspect compiled artifacts (starmap). │\n│ review Reviewer tooling (alpha 0: skeleton only — no commands yet). │\n│ inquiry Gaia Inquiry — semantic review loop (focus / review / reject / │\n│ obligation / hypothesis / tactics). │\n│ pkg Package operations (add / add-import / add-module / register / │\n│ scaffold). │\n│ author Optional authoring convenience (primary path: `gaia sdk` + write │\n│ the DSL directly). Writes into the package's authored/ submodule. │\n│ Verbs: claim / artifact / figure / equal / derive / note / question │\n│ / contradict / exclusive / decompose / observe / compute / infer / │\n│ associate / parameter / register-prior / variable / depends-on / │\n│ candidate-relation / materialize / composition / list. │\n│ bayes Bayesian-modelling authoring (model / compare / Binomial / │\n│ BetaBinomial / Normal / LogNormal / Beta / Exponential / Gamma / │\n│ StudentT / Cauchy / ChiSquared / Poisson). │\n│ example Print or save the cli walkthrough script for a shipping example │\n│ package (galileo / mendel). │\n│ skill Materialise bundled Gaia skills into cwd (register / list). │\n│ search Retrieval backends (lkm). │\n│ trace Gaia ARM Trace — verify and review execution traces (verify / │\n│ review / show). │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n What gaia does:\n Gaia turns a structured argument — claims, observations, derivations — into a\n compiled Bayesian network. Start with `gaia sdk` to get the SDK reference +\n cheat sheet, then author the DSL directly in Python (the primary path; `gaia\n author …` is an optional convenience). Compile with `gaia build compile`, run\n inference with `gaia run infer`, and render a slide-deck-ready artifact with\n `gaia run render`. See `gaia example galileo` for an end-to-end walkthrough.\n What is a knowledge package?\n A knowledge package is a small Python project that captures a structured\n argument — premises, derivations, observations — that gaia compiles into a\n Bayesian network. The shipped examples (`gaia example galileo`, `gaia example\n mendel`) walk through building one end-to-end.\n Typical authoring flow:\n $ gaia build init my-pkg-gaia\n $ gaia sdk # SDK reference + CHEATSHEET.md; author directly\n $ gaia build compile ./my-pkg-gaia\n $ gaia run infer ./my-pkg-gaia\n Run `gaia --help` for per-group verb references.\n Run `gaia example --help` to see how a full demo is authored.\n\n\n" } diff --git a/tests/cli/test_quality_gate.py b/tests/cli/test_quality_gate.py index b8e4b2880..ff6e7c7a9 100644 --- a/tests/cli/test_quality_gate.py +++ b/tests/cli/test_quality_gate.py @@ -143,10 +143,10 @@ def test_gate_fails_on_unreviewed_compose(tmp_path): pkg_dir = tmp_path / "gate_demo" _write_gate_package( pkg_dir, - "from gaia.engine.lang import Claim, compose, derive\n\n" + "from gaia.engine.lang import Claim, composition, derive\n\n" 'premise = Claim("A.")\n' 'premise.label = "premise"\n\n' - '@compose(name="test:workflow", version="1.0", label="workflow")\n' + '@composition(name="test:workflow", version="1.0", label="workflow")\n' "def workflow(a: Claim) -> Claim:\n" ' result = derive("C.", given=a, rationale="A implies C.", label="derive_c")\n' ' result.label = "c"\n' diff --git a/tests/gaia/lang/test_compiler_actions.py b/tests/gaia/lang/test_compiler_actions.py index e888e3def..60c21fae4 100644 --- a/tests/gaia/lang/test_compiler_actions.py +++ b/tests/gaia/lang/test_compiler_actions.py @@ -6,7 +6,7 @@ Variable, associate, candidate_relation, - compose, + composition, compute, contradict, depends_on, @@ -670,11 +670,11 @@ def test_duplicate_operator_action_label_raises(): def test_duplicate_compose_action_label_raises(): - @compose(name="test:workflow:first", version="1.0", label="same_workflow") + @composition(name="test:workflow:first", version="1.0", label="same_workflow") def first_workflow(seed: Claim) -> Claim: return derive("C1.", given=seed, rationale="First workflow.", label="first_child") - @compose(name="test:workflow:second", version="1.0", label="same_workflow") + @composition(name="test:workflow:second", version="1.0", label="same_workflow") def second_workflow(seed: Claim) -> Claim: return derive("C2.", given=seed, rationale="Second workflow.", label="second_child") diff --git a/tests/gaia/lang/test_composition.py b/tests/gaia/lang/test_composition.py index f71d07b6d..e6d10e709 100644 --- a/tests/gaia/lang/test_composition.py +++ b/tests/gaia/lang/test_composition.py @@ -1,3 +1,5 @@ +import pytest + import gaia.engine.bayes as bayes from gaia.engine.lang import ( Claim, @@ -6,6 +8,7 @@ associate, candidate_relation, compose, + composition, compute, derive, infer, @@ -34,7 +37,7 @@ def _strategy_by_id(compiled): def test_compose_returns_conclusion_and_compiles_action_dag(): - @compose(name="test:evidence:toy_likelihood", version="1.0") + @composition(name="test:evidence:toy_likelihood", version="1.0") def toy_likelihood(evidence: Claim, hypothesis: Claim) -> Claim: p_h = compute(Probability, fn=lambda: 0.8, label="compute_p_e_given_h") p_h.label = "p_e_given_h" @@ -103,7 +106,7 @@ def test_compose_infers_closed_over_infer_given_as_input(): gate = Claim("Gate.") gate.label = "gate" - @compose(name="test:evidence:gated", version="1.0") + @composition(name="test:evidence:gated", version="1.0") def gated_likelihood(evidence: Claim, hypothesis: Claim) -> Claim: return infer( evidence, @@ -134,7 +137,7 @@ def test_compose_keeps_own_background_and_warrants_separate_from_child_warrants( ) compose_warrant.label = "compose_warrant" - @compose( + @composition( name="test:association:toy", version="1.0", background=[bg], @@ -192,7 +195,7 @@ def test_compose_can_reference_bayes_model_and_compare_actions(): data = Claim("Observed k = 3.") data.label = "data" - @compose(name="test:bayes:single", version="1.0") + @composition(name="test:bayes:single", version="1.0") def workflow(hypothesis: Claim, observation: Claim) -> Claim: model = bayes.model( hypothesis, @@ -262,7 +265,7 @@ def test_compose_does_not_capture_scaffold_records_as_child_reasoning(): c = Claim("C.") c.label = "c" - @compose(name="test:scaffold-free", version="1.0") + @composition(name="test:scaffold-free", version="1.0") def workflow() -> Claim: candidate_relation(claims=[a, b], label="open_relation") return derive(c, given=[a], rationale="C follows from A.", label="derive_c") @@ -275,3 +278,23 @@ def workflow() -> Claim: compiled.action_label_map["github:v6_scaffold_composition::action::derive_c"] ] assert compiled.formalization_manifest["dependencies"][0]["label"] == "open_relation" + + +def test_compose_alias_is_deprecated_and_delegates_to_composition(): + with CollectedPackage("v6_composition") as pkg: + e = Claim("Evidence holds.") + deprecation = r"compose\(\) is deprecated; use composition\(\)" + with pytest.warns(DeprecationWarning, match=deprecation): + decorator = compose(name="test:evidence:deprecated-alias", version="1.0") + + @decorator + def deprecated_pattern() -> Claim: + return e + + result = deprecated_pattern() + + assert result is e + compose_actions = [action for action in pkg.actions if isinstance(action, Compose)] + assert len(compose_actions) == 1 + assert compose_actions[0].name == "test:evidence:deprecated-alias" + assert compose_actions[0].version == "1.0"