Skip to content

compose_cmd

compose_cmd

jarvis compose — unified composition CLI for discrete agents and operators.

Functions

compose

compose() -> None

Compose, run, benchmark, and deploy OpenJarvis configurations.

Recipes are unified TOML configs that wire all five primitives (Intelligence, Engine, Agent, Tools, Learning). They come in two kinds:

 discrete One-shot or benchmark-oriented agents operator Persistent, scheduled autonomous agents

Source code in src/openjarvis/cli/compose_cmd.py
@click.group()
def compose() -> None:
    """Compose, run, benchmark, and deploy OpenJarvis configurations.

    Recipes are unified TOML configs that wire all five primitives
    (Intelligence, Engine, Agent, Tools, Learning).  They come in two
    kinds:

    \b
      discrete   One-shot or benchmark-oriented agents
      operator   Persistent, scheduled autonomous agents
    """

compose_list

compose_list(kind: Optional[str]) -> None

List all discovered compositions (recipes and operators).

Source code in src/openjarvis/cli/compose_cmd.py
@compose.command("list")
@click.option(
    "-k", "--kind", "kind", default=None,
    type=click.Choice(["discrete", "operator"]),
    help="Filter by recipe kind.",
)
def compose_list(kind: Optional[str]) -> None:
    """List all discovered compositions (recipes and operators)."""
    console = Console(stderr=True)
    try:
        from openjarvis.recipes.loader import discover_recipes

        recipes = discover_recipes(kind=kind)
        if not recipes:
            console.print("[dim]No compositions found.[/dim]")
            console.print(
                "[dim]Place TOML recipes in src/openjarvis/recipes/data/ "
                "or ~/.openjarvis/recipes/[/dim]"
            )
            return

        table = Table(title="Compositions", border_style="bright_blue")
        table.add_column("Name", style="cyan", no_wrap=True)
        table.add_column("Kind", style="yellow")
        table.add_column("Model", style="green")
        table.add_column("Agent", style="magenta")
        table.add_column("Tools", style="white")
        table.add_column("Description")

        for r in sorted(recipes, key=lambda r: (r.kind, r.name)):
            tools_str = ", ".join(r.tools[:3])
            if len(r.tools) > 3:
                tools_str += f" (+{len(r.tools) - 3})"
            table.add_row(
                r.name,
                r.kind,
                r.model or "-",
                r.agent_type or "-",
                tools_str or "-",
                r.description[:60] if r.description else "",
            )

        console.print(table)
    except Exception as exc:
        console.print(f"[red]Error: {exc}[/red]")

compose_show

compose_show(name: str) -> None

Show detailed configuration of a composition.

Source code in src/openjarvis/cli/compose_cmd.py
@compose.command("show")
@click.argument("name")
def compose_show(name: str) -> None:
    """Show detailed configuration of a composition."""
    console = Console(stderr=True)
    try:
        from openjarvis.recipes.loader import resolve_recipe

        recipe = resolve_recipe(name)
        if recipe is None:
            console.print(f"[red]Composition not found: {name}[/red]")
            return

        console.print(f"[bold cyan]{recipe.name}[/bold cyan]  ({recipe.kind})")
        console.print(f"  {recipe.description}\n")

        # Intelligence
        console.print("[bold]Intelligence[/bold]")
        console.print(f"  model:        {recipe.model or '-'}")
        console.print(f"  quantization: {recipe.quantization or '-'}")
        console.print(f"  provider:     {recipe.provider or '-'}")
        console.print()

        # Engine
        console.print("[bold]Engine[/bold]")
        console.print(f"  key: {recipe.engine_key or '-'}")
        console.print()

        # Agent
        console.print("[bold]Agent[/bold]")
        console.print(f"  type:        {recipe.agent_type or '-'}")
        console.print(f"  max_turns:   {recipe.max_turns or '-'}")
        console.print(f"  temperature: {recipe.temperature or '-'}")
        console.print(f"  tools:       {', '.join(recipe.tools) or '-'}")
        if recipe.system_prompt:
            preview = recipe.system_prompt[:120].replace("\n", " ")
            console.print(f"  prompt:      {preview}...")
        console.print()

        # Learning
        console.print("[bold]Learning[/bold]")
        console.print(f"  routing: {recipe.routing_policy or '-'}")
        console.print(f"  agent:   {recipe.agent_policy or '-'}")
        console.print()

        # Kind-specific sections
        if recipe.kind == "discrete":
            benchmarks = recipe.eval_benchmarks or recipe.eval_suites
            if benchmarks:
                console.print("[bold]Eval[/bold]")
                console.print(f"  benchmarks:   {', '.join(benchmarks)}")
                console.print(f"  backend:      {recipe.eval_backend or 'auto'}")
                console.print(f"  max_samples:  {recipe.eval_max_samples or 'all'}")
                console.print(f"  judge_model:  {recipe.eval_judge_model or 'default'}")
        elif recipe.kind == "operator":
            if recipe.schedule_type:
                console.print("[bold]Schedule[/bold]")
                console.print(f"  type:  {recipe.schedule_type}")
                console.print(f"  value: {recipe.schedule_value}")
            if recipe.channels:
                console.print("[bold]Channels[/bold]")
                console.print(f"  output: {', '.join(recipe.channels)}")
    except Exception as exc:
        console.print(f"[red]Error: {exc}[/red]")

compose_run

compose_run(name: str, query: tuple[str, ...], output_json: bool) -> None

Run a composition against a single query.

Source code in src/openjarvis/cli/compose_cmd.py
@compose.command("run")
@click.argument("name")
@click.argument("query", nargs=-1, required=True)
@click.option("--json", "output_json", is_flag=True, help="Output raw JSON result.")
def compose_run(name: str, query: tuple[str, ...], output_json: bool) -> None:
    """Run a composition against a single query."""
    console = Console(stderr=True)
    query_text = " ".join(query)

    try:
        from openjarvis.recipes.loader import resolve_recipe

        recipe = resolve_recipe(name)
        if recipe is None:
            console.print(f"[red]Composition not found: {name}[/red]")
            sys.exit(1)

        kwargs = recipe.to_builder_kwargs()
        console.print(
            f"[dim]Running [cyan]{recipe.name}[/cyan] "
            f"({recipe.agent_type or 'direct'} / "
            f"{recipe.model or 'default'})...[/dim]"
        )

        from openjarvis.system import SystemBuilder

        builder = SystemBuilder()
        if "engine_key" in kwargs:
            builder = builder.engine(kwargs["engine_key"])
        if "model" in kwargs:
            builder = builder.model(kwargs["model"])
        if "agent" in kwargs:
            builder = builder.agent(kwargs["agent"])
        if "tools" in kwargs:
            builder = builder.tools(kwargs["tools"])

        system = builder.build()

        try:
            agent_kwargs = {}
            if kwargs.get("system_prompt"):
                agent_kwargs["system_prompt"] = kwargs["system_prompt"]
            if kwargs.get("max_turns"):
                agent_kwargs["max_turns"] = kwargs["max_turns"]
            if kwargs.get("temperature"):
                agent_kwargs["temperature"] = kwargs["temperature"]

            result = system.ask(query_text, **agent_kwargs)

            if output_json:
                import json as json_mod
                if isinstance(result, str):
                    click.echo(json_mod.dumps({"content": result}, indent=2))
                else:
                    click.echo(json_mod.dumps({
                        "content": result.content,
                        "turns": getattr(result, "turns", 1),
                    }, indent=2))
            else:
                content = result if isinstance(result, str) else result.content
                click.echo(content)
        finally:
            system.close()
    except Exception as exc:
        console.print(f"[red]Error: {exc}[/red]")
        sys.exit(1)

compose_bench

compose_bench(name: str, benchmark: tuple[str, ...], max_samples: Optional[int], judge_model: Optional[str], verbose: bool) -> None

Benchmark a discrete composition against eval datasets.

Uses the recipe's model, engine, agent, and tools to run the eval framework against the benchmarks defined in the recipe (or overridden via --benchmark).

Source code in src/openjarvis/cli/compose_cmd.py
@compose.command("bench")
@click.argument("name")
@click.option(
    "-b", "--benchmark", "benchmark", default=None, multiple=True,
    help="Override benchmarks (can specify multiple).",
)
@click.option(
    "-n", "--max-samples", "max_samples", type=int, default=None,
    help="Maximum samples per benchmark.",
)
@click.option(
    "--judge", "judge_model", default=None,
    help="LLM judge model override.",
)
@click.option(
    "-v", "--verbose", "verbose", is_flag=True, default=False,
    help="Verbose logging.",
)
def compose_bench(
    name: str,
    benchmark: tuple[str, ...],
    max_samples: Optional[int],
    judge_model: Optional[str],
    verbose: bool,
) -> None:
    """Benchmark a discrete composition against eval datasets.

    Uses the recipe's model, engine, agent, and tools to run the eval
    framework against the benchmarks defined in the recipe (or overridden
    via --benchmark).
    """
    console = Console(stderr=True)

    try:
        from openjarvis.recipes.loader import resolve_recipe

        recipe = resolve_recipe(name)
        if recipe is None:
            console.print(f"[red]Composition not found: {name}[/red]")
            sys.exit(1)

        benchmarks_list = list(benchmark) if benchmark else None
        suite = recipe.to_eval_suite(
            benchmarks=benchmarks_list,
            max_samples=max_samples,
            judge_model=judge_model,
        )

        from openjarvis.evals.core.config import expand_suite

        run_configs = expand_suite(suite)

        console.print(
            f"[cyan]Composition:[/cyan] {recipe.name}\n"
            f"[cyan]Model:[/cyan]       {recipe.model}\n"
            f"[cyan]Agent:[/cyan]       {recipe.agent_type or 'direct'}\n"
            f"[cyan]Tools:[/cyan]       {', '.join(recipe.tools) or 'none'}\n"
            f"[cyan]Benchmarks:[/cyan]  {len(run_configs)} run(s)"
        )

        try:
            from openjarvis.evals.cli import _run_single
        except ImportError:
            console.print("[red]Eval CLI module not available.[/red]")
            sys.exit(1)

        results_table = Table(
            title="Benchmark Results",
            border_style="bright_blue",
            title_style="bold cyan",
        )
        results_table.add_column("Benchmark", style="cyan")
        results_table.add_column("Accuracy", justify="right", style="bold green")
        results_table.add_column("Correct / Scored", justify="right")
        results_table.add_column("Errors", justify="right", style="red")

        for i, rc in enumerate(run_configs, 1):
            console.print(
                f"\n[bold]Run {i}/{len(run_configs)}:[/bold] {rc.benchmark}"
            )
            try:
                summary = _run_single(rc, console=console)
                results_table.add_row(
                    rc.benchmark,
                    f"{summary.accuracy:.4f}",
                    f"{summary.correct}/{summary.scored_samples}",
                    str(summary.errors),
                )
            except Exception as exc:
                console.print(f"  [red bold]FAILED:[/red bold] {exc}")
                results_table.add_row(rc.benchmark, "-", "-", str(exc)[:40])

        console.print()
        console.print(results_table)
    except Exception as exc:
        console.print(f"[red]Error: {exc}[/red]")
        sys.exit(1)

compose_deploy

compose_deploy(name: str) -> None

Deploy an operator composition (activate its scheduler task).

Source code in src/openjarvis/cli/compose_cmd.py
@compose.command("deploy")
@click.argument("name")
def compose_deploy(name: str) -> None:
    """Deploy an operator composition (activate its scheduler task)."""
    console = Console(stderr=True)

    try:
        from openjarvis.recipes.loader import resolve_recipe

        recipe = resolve_recipe(name)
        if recipe is None:
            console.print(f"[red]Composition not found: {name}[/red]")
            sys.exit(1)

        if recipe.kind != "operator":
            console.print(
                f"[red]Recipe '{name}' is a {recipe.kind} composition, "
                f"not an operator.  Only operators can be deployed.[/red]"
            )
            sys.exit(1)

        manifest = recipe.to_operator_manifest()

        from openjarvis.operators.manager import OperatorManager
        from openjarvis.system import SystemBuilder

        system = SystemBuilder().scheduler(True).sessions(True).build()
        manager = OperatorManager(system)
        system.operator_manager = manager

        manager.register(manifest)
        task_id = manager.activate(manifest.id)

        console.print(
            f"[green]Deployed operator [cyan]{name}[/cyan] "
            f"(task: {task_id}, schedule: "
            f"{recipe.schedule_type}={recipe.schedule_value})[/green]"
        )
    except Exception as exc:
        console.print(f"[red]Error: {exc}[/red]")
        sys.exit(1)

compose_stop

compose_stop(name: str) -> None

Stop a deployed operator composition.

Source code in src/openjarvis/cli/compose_cmd.py
@compose.command("stop")
@click.argument("name")
def compose_stop(name: str) -> None:
    """Stop a deployed operator composition."""
    console = Console(stderr=True)

    try:
        from openjarvis.operators.manager import OperatorManager
        from openjarvis.system import SystemBuilder

        system = SystemBuilder().scheduler(True).sessions(True).build()
        manager = OperatorManager(system)
        system.operator_manager = manager

        # Discover all known operators so the manager knows about them
        from openjarvis.core.config import DEFAULT_CONFIG_DIR
        from openjarvis.recipes.loader import _PROJECT_OPERATORS_DIR

        for d in [DEFAULT_CONFIG_DIR / "operators", _PROJECT_OPERATORS_DIR]:
            if d.is_dir():
                manager.discover(d)

        manager.deactivate(name)
        console.print(f"[yellow]Stopped operator {name}[/yellow]")
    except Exception as exc:
        console.print(f"[red]Error: {exc}[/red]")
        sys.exit(1)

compose_status

compose_status() -> None

Show status of all deployed operators.

Source code in src/openjarvis/cli/compose_cmd.py
@compose.command("status")
def compose_status() -> None:
    """Show status of all deployed operators."""
    console = Console(stderr=True)

    try:
        from openjarvis.operators.manager import OperatorManager
        from openjarvis.system import SystemBuilder

        system = SystemBuilder().scheduler(True).sessions(True).build()
        manager = OperatorManager(system)
        system.operator_manager = manager

        from openjarvis.core.config import DEFAULT_CONFIG_DIR
        from openjarvis.recipes.loader import _PROJECT_OPERATORS_DIR

        for d in [DEFAULT_CONFIG_DIR / "operators", _PROJECT_OPERATORS_DIR]:
            if d.is_dir():
                manager.discover(d)

        statuses = manager.status()
        if not statuses:
            console.print("[dim]No operators registered.[/dim]")
            return

        table = Table(title="Operator Status", border_style="bright_blue")
        table.add_column("Name", style="cyan")
        table.add_column("State", style="yellow")
        table.add_column("Schedule", style="white")
        table.add_column("Last Run", style="dim")

        for s in statuses:
            table.add_row(
                s.get("id", "?"),
                s.get("state", "unknown"),
                s.get("schedule", ""),
                s.get("last_run", "-"),
            )
        console.print(table)
    except Exception as exc:
        console.print(f"[red]Error: {exc}[/red]")