Skip to content

Index

recipes

Recipe system — composable primitive configurations.

Classes

Recipe dataclass

Recipe(name: str, description: str = '', version: str = '0.1.0', kind: str = 'discrete', model: Optional[str] = None, quantization: Optional[str] = None, provider: Optional[str] = None, engine_key: Optional[str] = None, agent_type: Optional[str] = None, max_turns: Optional[int] = None, temperature: Optional[float] = None, max_tokens: Optional[int] = None, tools: List[str] = list(), system_prompt: Optional[str] = None, system_prompt_path: Optional[str] = None, routing_policy: Optional[str] = None, agent_policy: Optional[str] = None, eval_suites: List[str] = list(), eval_benchmarks: List[str] = list(), eval_backend: Optional[str] = None, eval_max_samples: Optional[int] = None, eval_judge_model: Optional[str] = None, schedule_type: Optional[str] = None, schedule_value: Optional[str] = None, channels: List[str] = list(), required_capabilities: List[str] = list(), raw: Dict[str, Any] = dict())

A composable primitive configuration loaded from TOML.

Covers both discrete agents (benchmarking / one-shot) and operator agents (persistent / scheduled) through the kind field.

Functions
to_builder_kwargs
to_builder_kwargs() -> Dict[str, Any]

Convert recipe fields to kwargs for SystemBuilder/Jarvis.

Returns a dict with only the non-None fields, keyed to match the SystemBuilder fluent API or Jarvis constructor parameters.

Source code in src/openjarvis/recipes/loader.py
def to_builder_kwargs(self) -> Dict[str, Any]:
    """Convert recipe fields to kwargs for SystemBuilder/Jarvis.

    Returns a dict with only the non-None fields, keyed to match
    the SystemBuilder fluent API or Jarvis constructor parameters.
    """
    kwargs: Dict[str, Any] = {}
    if self.model is not None:
        kwargs["model"] = self.model
    if self.engine_key is not None:
        kwargs["engine_key"] = self.engine_key
    if self.agent_type is not None:
        kwargs["agent"] = self.agent_type
    if self.tools:
        kwargs["tools"] = self.tools
    if self.temperature is not None:
        kwargs["temperature"] = self.temperature
    if self.max_turns is not None:
        kwargs["max_turns"] = self.max_turns
    prompt = self.system_prompt
    if prompt is None and self.system_prompt_path is not None:
        p = Path(self.system_prompt_path)
        if p.exists():
            prompt = p.read_text(encoding="utf-8")
    if prompt is not None:
        kwargs["system_prompt"] = prompt
    if self.routing_policy is not None:
        kwargs["routing_policy"] = self.routing_policy
    if self.agent_policy is not None:
        kwargs["agent_policy"] = self.agent_policy
    if self.quantization is not None:
        kwargs["quantization"] = self.quantization
    if self.provider is not None:
        kwargs["provider"] = self.provider
    if self.eval_suites:
        kwargs["eval_suites"] = self.eval_suites
    return kwargs
to_eval_suite
to_eval_suite(benchmarks: Optional[List[str]] = None, max_samples: Optional[int] = None, judge_model: Optional[str] = None) -> Any

Convert this recipe into an EvalSuiteConfig.

Uses the recipe's model/engine as the single [[models]] entry and the recipe's benchmarks (or benchmarks override) as [[benchmarks]], inheriting agent type and tools.

Source code in src/openjarvis/recipes/loader.py
def to_eval_suite(
    self,
    benchmarks: Optional[List[str]] = None,
    max_samples: Optional[int] = None,
    judge_model: Optional[str] = None,
) -> Any:
    """Convert this recipe into an ``EvalSuiteConfig``.

    Uses the recipe's model/engine as the single ``[[models]]`` entry
    and the recipe's benchmarks (or *benchmarks* override) as
    ``[[benchmarks]]``, inheriting agent type and tools.
    """
    from openjarvis.recipes.composer import recipe_to_eval_suite

    return recipe_to_eval_suite(
        self,
        benchmarks=benchmarks,
        max_samples=max_samples,
        judge_model=judge_model,
    )
to_operator_manifest
to_operator_manifest() -> Any

Convert this recipe into an OperatorManifest.

Source code in src/openjarvis/recipes/loader.py
def to_operator_manifest(self) -> Any:
    """Convert this recipe into an ``OperatorManifest``."""
    from openjarvis.recipes.composer import recipe_to_operator

    return recipe_to_operator(self)

Functions

recipe_to_eval_suite

recipe_to_eval_suite(recipe: Recipe, benchmarks: Optional[List[str]] = None, max_samples: Optional[int] = None, judge_model: Optional[str] = None) -> EvalSuiteConfig

Build an EvalSuiteConfig from a recipe.

The recipe's model / engine become the single [[models]] entry. The recipe's eval_benchmarks (or the benchmarks override) become [[benchmarks]] entries. Agent type and tools are inherited so the eval runner constructs the right backend automatically.

Args: recipe: Source recipe. benchmarks: Override benchmark list (defaults to recipe.eval_benchmarks). max_samples: Override per-benchmark sample cap. judge_model: Override LLM judge model.

Raises: ValueError: If no model or benchmarks can be resolved.

Source code in src/openjarvis/recipes/composer.py
def recipe_to_eval_suite(
    recipe: Recipe,
    benchmarks: Optional[List[str]] = None,
    max_samples: Optional[int] = None,
    judge_model: Optional[str] = None,
) -> EvalSuiteConfig:
    """Build an ``EvalSuiteConfig`` from a recipe.

    The recipe's model / engine become the single ``[[models]]`` entry.
    The recipe's ``eval_benchmarks`` (or the *benchmarks* override) become
    ``[[benchmarks]]`` entries.  Agent type and tools are inherited so the
    eval runner constructs the right backend automatically.

    Args:
        recipe: Source recipe.
        benchmarks: Override benchmark list (defaults to ``recipe.eval_benchmarks``).
        max_samples: Override per-benchmark sample cap.
        judge_model: Override LLM judge model.

    Raises:
        ValueError: If no model or benchmarks can be resolved.
    """
    from openjarvis.evals.core.types import (
        BenchmarkConfig,
        DefaultsConfig,
        EvalSuiteConfig,
        ExecutionConfig,
        JudgeConfig,
        MetaConfig,
        ModelConfig,
    )

    bench_names = benchmarks or list(recipe.eval_benchmarks)
    if not bench_names:
        bench_names = list(recipe.eval_suites)
    if not bench_names:
        raise ValueError(
            f"Recipe '{recipe.name}' has no benchmarks defined and none were "
            "provided.  Set [eval] benchmarks in the TOML or pass benchmarks=."
        )

    model_name = recipe.model
    if not model_name:
        raise ValueError(
            f"Recipe '{recipe.name}' has no model defined.  "
            "Set [intelligence] model in the TOML."
        )

    has_agent = recipe.agent_type is not None
    backend = recipe.eval_backend or ("jarvis-agent" if has_agent else "jarvis-direct")

    model_cfg = ModelConfig(
        name=model_name,
        engine=recipe.engine_key,
        provider=recipe.provider,
        temperature=recipe.temperature,
    )

    bench_cfgs: list[BenchmarkConfig] = []
    for bname in bench_names:
        bench_cfgs.append(BenchmarkConfig(
            name=bname,
            backend=backend,
            max_samples=max_samples or recipe.eval_max_samples,
            agent=recipe.agent_type if has_agent else None,
            tools=list(recipe.tools) if has_agent else [],
            judge_model=judge_model or recipe.eval_judge_model,
        ))

    return EvalSuiteConfig(
        meta=MetaConfig(
            name=f"{recipe.name}-eval",
            description=f"Auto-generated eval suite from recipe '{recipe.name}'",
        ),
        defaults=DefaultsConfig(
            temperature=recipe.temperature or 0.0,
            max_tokens=2048,
        ),
        judge=JudgeConfig(
            model=judge_model or recipe.eval_judge_model or "gpt-5-mini-2025-08-07",
        ),
        run=ExecutionConfig(),
        models=[model_cfg],
        benchmarks=bench_cfgs,
    )

recipe_to_operator

recipe_to_operator(recipe: Recipe) -> OperatorManifest

Build an OperatorManifest from a recipe.

Maps the recipe's agent, schedule, and channel fields into the operator manifest format used by OperatorManager.

Raises: ValueError: If schedule information is missing.

Source code in src/openjarvis/recipes/composer.py
def recipe_to_operator(recipe: Recipe) -> OperatorManifest:
    """Build an ``OperatorManifest`` from a recipe.

    Maps the recipe's agent, schedule, and channel fields into the
    operator manifest format used by ``OperatorManager``.

    Raises:
        ValueError: If schedule information is missing.
    """
    from openjarvis.operators.types import OperatorManifest

    if not recipe.schedule_type:
        raise ValueError(
            f"Recipe '{recipe.name}' has no [schedule] section.  "
            "Operator recipes must define schedule_type and schedule_value."
        )

    prompt = recipe.system_prompt or ""
    prompt_path = recipe.system_prompt_path or ""

    return OperatorManifest(
        id=recipe.name,
        name=recipe.name,
        version=recipe.version,
        description=recipe.description,
        tools=list(recipe.tools),
        system_prompt=prompt,
        system_prompt_path=prompt_path,
        max_turns=recipe.max_turns or 20,
        temperature=recipe.temperature or 0.3,
        schedule_type=recipe.schedule_type,
        schedule_value=recipe.schedule_value or "300",
        required_capabilities=list(recipe.required_capabilities),
    )

discover_recipes

discover_recipes(extra_dirs: Optional[List[str | Path]] = None, *, kind: Optional[str] = None) -> List[Recipe]

Discover all TOML recipes from known directories.

Search order (later entries override earlier ones by name): 1. Project recipes/data/ directory (discrete recipes) 2. Project recipes/data/operators/ directory (operator recipes) 3. User ~/.openjarvis/recipes/ directory 4. User ~/.openjarvis/operators/ directory 5. Any additional directories in extra_dirs

Args: extra_dirs: Additional directories to scan. kind: If set, filter to only "discrete" or "operator" recipes.

Source code in src/openjarvis/recipes/loader.py
def discover_recipes(
    extra_dirs: Optional[List[str | Path]] = None,
    *,
    kind: Optional[str] = None,
) -> List[Recipe]:
    """Discover all TOML recipes from known directories.

    Search order (later entries override earlier ones by name):
    1. Project ``recipes/data/`` directory (discrete recipes)
    2. Project ``recipes/data/operators/`` directory (operator recipes)
    3. User ``~/.openjarvis/recipes/`` directory
    4. User ``~/.openjarvis/operators/`` directory
    5. Any additional directories in *extra_dirs*

    Args:
        extra_dirs: Additional directories to scan.
        kind: If set, filter to only "discrete" or "operator" recipes.
    """
    dirs: List[Path] = [
        _PROJECT_RECIPES_DIR,
        _PROJECT_OPERATORS_DIR,
        _USER_RECIPES_DIR,
        _USER_OPERATORS_DIR,
    ]
    if extra_dirs:
        dirs.extend(Path(d) for d in extra_dirs)

    recipes: Dict[str, Recipe] = {}
    for d in dirs:
        if not d.is_dir():
            continue
        for toml_path in sorted(d.glob("*.toml")):
            try:
                recipe = load_recipe(toml_path)
                if kind is None or recipe.kind == kind:
                    recipes[recipe.name] = recipe
            except Exception:
                continue

    return list(recipes.values())

load_recipe

load_recipe(path: str | Path) -> Recipe

Load a recipe from a TOML file.

Supports the unified format with [recipe], [intelligence], [engine], [agent], [learning], [eval], [schedule], and [channels] sections. Also auto-detects legacy operator manifests that use [operator] as the top-level key.

Raises: FileNotFoundError: If path does not exist.

Source code in src/openjarvis/recipes/loader.py
def load_recipe(path: str | Path) -> Recipe:
    """Load a recipe from a TOML file.

    Supports the unified format with ``[recipe]``, ``[intelligence]``,
    ``[engine]``, ``[agent]``, ``[learning]``, ``[eval]``, ``[schedule]``,
    and ``[channels]`` sections.  Also auto-detects legacy operator manifests
    that use ``[operator]`` as the top-level key.

    Raises:
        FileNotFoundError: If *path* does not exist.
    """
    path = Path(path)
    if not path.exists():
        raise FileNotFoundError(f"Recipe file not found: {path}")

    with open(path, "rb") as fh:
        data = tomllib.load(fh)

    # Auto-detect legacy operator manifests ([operator] key)
    if "operator" in data and "recipe" not in data:
        return _load_operator_as_recipe(path, data)

    recipe_sec = data.get("recipe", {})
    intel_sec = data.get("intelligence", {})
    engine_sec = data.get("engine", {})
    agent_sec = data.get("agent", {})
    learning_sec = data.get("learning", {})
    eval_sec = data.get("eval", {})
    schedule_sec = data.get("schedule", {})
    channels_sec = data.get("channels", {})

    system_prompt = agent_sec.get("system_prompt")
    system_prompt_path = agent_sec.get("system_prompt_path")

    # Resolve external prompt relative to TOML file
    if not system_prompt and system_prompt_path:
        prompt_p = Path(system_prompt_path)
        if not prompt_p.is_absolute():
            prompt_p = path.parent / prompt_p
        if prompt_p.exists():
            system_prompt = prompt_p.read_text(encoding="utf-8")
            system_prompt_path = str(prompt_p)

    kind = recipe_sec.get("kind", "discrete")
    if schedule_sec and kind == "discrete":
        kind = "operator"

    return Recipe(
        name=recipe_sec.get("name", path.stem),
        description=recipe_sec.get("description", ""),
        version=recipe_sec.get("version", "0.1.0"),
        kind=kind,
        model=intel_sec.get("model"),
        quantization=intel_sec.get("quantization"),
        provider=intel_sec.get("provider") or engine_sec.get("provider"),
        engine_key=engine_sec.get("key"),
        agent_type=agent_sec.get("type"),
        max_turns=agent_sec.get("max_turns"),
        temperature=agent_sec.get("temperature"),
        tools=agent_sec.get("tools", []),
        system_prompt=system_prompt,
        system_prompt_path=system_prompt_path,
        routing_policy=learning_sec.get("routing"),
        agent_policy=learning_sec.get("agent"),
        eval_suites=eval_sec.get("suites", []),
        eval_benchmarks=eval_sec.get("benchmarks", []),
        eval_backend=eval_sec.get("backend"),
        eval_max_samples=eval_sec.get("max_samples"),
        eval_judge_model=eval_sec.get("judge_model"),
        schedule_type=schedule_sec.get("type"),
        schedule_value=str(schedule_sec["value"]) if "value" in schedule_sec else None,
        channels=channels_sec.get("output", []),
        required_capabilities=recipe_sec.get("required_capabilities", []),
        raw=data,
    )

resolve_recipe

resolve_recipe(name: str) -> Optional[Recipe]

Find a recipe by name from all known directories.

Returns None if no recipe with the given name is found.

Source code in src/openjarvis/recipes/loader.py
def resolve_recipe(name: str) -> Optional[Recipe]:
    """Find a recipe by name from all known directories.

    Returns ``None`` if no recipe with the given name is found.
    """
    for recipe in discover_recipes():
        if recipe.name == name:
            return recipe
    return None