Skip to content

compose

compose

Compose command — mzt compose.

Compiles semantic agent definitions into Mozart scores. Takes a YAML config describing agents as people (voice, focus, meditation, techniques) and produces complete self-chaining scores with identity seeding, technique wiring, instrument resolution, and validation generation.

Usage::

mzt compose config.yaml --output scores/my-project/
mzt compose config.yaml --output scores/ --fleet
mzt compose config.yaml --dry-run
mzt compose config.yaml --seed-only

Classes

Functions

compose

compose(config=Argument(..., help='Path to the semantic agent config YAML'), output=Option(None, '--output', '-o', help='Output directory for generated scores'), fleet=Option(False, '--fleet', help='Force generation of a fleet config (auto-generated for multi-agent configs)'), dry_run=Option(False, '--dry-run', help='Show what would be generated without writing files'), seed_only=Option(False, '--seed-only', help="Only seed agent identities, don't generate scores"), agents_dir=Option(None, '--agents-dir', help='Override agents identity directory (default: ~/.mzt/agents)'), techniques_dir=Option(None, '--techniques-dir', help='Path to technique module documents'))

Compile semantic agent definitions into Mozart scores.

Takes a YAML config describing agents and produces complete self-chaining scores with identity, techniques, instruments, and validations.

Source code in src/marianne/cli/commands/compose.py
def compose(
    config: Path = typer.Argument(
        ...,
        help="Path to the semantic agent config YAML",
    ),
    output: Path | None = typer.Option(
        None,
        "--output", "-o",
        help="Output directory for generated scores",
    ),
    fleet: bool = typer.Option(
        False,
        "--fleet",
        help="Force generation of a fleet config (auto-generated for multi-agent configs)",
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run",
        help="Show what would be generated without writing files",
    ),
    seed_only: bool = typer.Option(
        False,
        "--seed-only",
        help="Only seed agent identities, don't generate scores",
    ),
    agents_dir: Path | None = typer.Option(
        None,
        "--agents-dir",
        help="Override agents identity directory (default: ~/.mzt/agents)",
    ),
    techniques_dir: Path | None = typer.Option(
        None,
        "--techniques-dir",
        help="Path to technique module documents",
    ),
) -> None:
    """Compile semantic agent definitions into Mozart scores.

    Takes a YAML config describing agents and produces complete
    self-chaining scores with identity, techniques, instruments,
    and validations.
    """
    import yaml

    from marianne.compose.pipeline import CompilationPipeline

    # Load config
    try:
        with open(config) as f:
            config_data = yaml.safe_load(f) or {}
    except yaml.YAMLError as e:
        output_error(
            f"YAML syntax error: {e}",
            hints=[f"Check indentation and syntax in {config}"],
        )
        raise typer.Exit(1) from None
    except FileNotFoundError:
        output_error(
            f"Config file not found: {config}",
            hints=["Check the file path and try again"],
        )
        raise typer.Exit(1) from None

    if not isinstance(config_data, dict):
        output_error(
            "Config file must contain a YAML mapping",
            hints=["Ensure the file starts with key-value pairs, not a list"],
        )
        raise typer.Exit(1) from None

    agents = config_data.get("agents", [])
    if not agents:
        output_error(
            "Config must contain at least one agent",
            hints=["Add an 'agents' list with at least one agent definition"],
        )
        raise typer.Exit(1)

    project = config_data.get("project", {})
    project_name = project.get("name", config.stem) if isinstance(project, dict) else config.stem

    if dry_run:
        _show_dry_run(config_data, project_name)
        return

    pipeline = CompilationPipeline(
        agents_dir=agents_dir,
        techniques_dir=techniques_dir,
    )

    if seed_only:
        _seed_identities(pipeline, config_data)
        return

    # Compile all agent scores
    output_dir = output or (config.parent / "scores")
    score_paths = pipeline.compile_config(config_data, output_dir)

    # Force fleet generation when --fleet is passed and pipeline didn't already
    # generate one (pipeline auto-generates fleet for multi-agent configs)
    if fleet and not any(p.name == "fleet.yaml" for p in score_paths):
        from marianne.compose.fleet import FleetGenerator

        fleet_path = output_dir / "fleet.yaml"
        FleetGenerator().write(config_data, output_dir, fleet_path)
        score_paths.append(fleet_path)
        _logger.info("fleet_config_forced", path=str(fleet_path))

    console.print(f"\n[green]Compiled {len(agents)} agent scores[/green]")
    for path in score_paths:
        console.print(f"  {path}")
    console.print(f"\nScores written to: {output_dir}")