ExecutionPlan. Prefer one default Recipe per Benchmark that works with every Environment supporting the task’s requirements. Keep optional strategies, such as alternative runner images, separate from provider selection.
A Recipe is planning policy. It may select an image, workspace, resources, network settings, or a specialized Benchmark or Harness plan, but it must not open a sandbox, install packages, call a model or provider, run a task, or score a result. Put those side effects in the lifecycle component that owns them.
Choose the Integration Form
External Recipe code executes in the AgentCompass process, outside the task sandbox. Only load packages you have reviewed and trust.
Implement the Base Contract
Every Recipe is a zero-argument constructibleBaseRecipe with a unique id, matches(), and apply():
matches(req, task, plan) only to decide whether the Recipe applies. It receives the current plan, including changes made by earlier matching Recipes. It must not mutate any argument.
Use apply(plan, req, task) to return a new ExecutionPlan. Deep-copy the incoming plan before changing nested Environment, Benchmark, or Harness plan fields. Preserve explicit compatible user values: fill missing fields with setdefault() or an equivalent fallback, and reject an incompatible explicit value with an actionable error instead of silently replacing it.
Both methods belong to deterministic planning. Keep them free of network access, file writes, subprocesses, package installation, sandbox creation, model calls, and other externally visible side effects.
Use shared fields such as environment.setup, resources, phase environment variables, and network policies. The Environment translates those requirements into native provider settings. Check capabilities rather than listing provider IDs; environment_supports_task_image() reads the registered class without constructing a provider or client. Adding a provider with the same capabilities should require no Benchmark or Recipe changes. Required capabilities are validated by the runtime before allocation; an optional image strategy can fall back to source preparation when images are unsupported.
Register a Built-in Recipe
Place a public implementation undersrc/agentcompass/recipes/, then register it:
agentcompass.recipes so its decorator runs. The registry uses id as its key, and each registered ID must be unique. Select a Recipe by that ID; run records store the same ID. Keep native provider translation in the Environment and task-specific planning in the Recipe.
Built-in Recipes must target only public providers and public infrastructure. Keep organization-specific deployment policy in a trusted external package instead.
Package a Trusted External Recipe
An external Recipe directory is a Python package, not a loose Python file:RECIPES registry:
- the directory exists and contains
__init__.py; RECIPE_CLASSESis a non-empty list or tuple;- every item is a concrete
BaseRecipesubclass with a non-empty, uniqueid; - every class supports zero-argument construction.
example_exact_match and example_answer from the Benchmark and Harness implementation tutorials, load and allow the Recipe for their deterministic task:
--recipe is an allowlist, not a force-run switch. The Recipe still needs to return True from matches(). See Recipes for the CLI, SDK, configuration-file, and orchestration equivalents.
Understand Application Order
The current Planner builds one default plan for an attempt and then walks the run-local Recipe registry in insertion order:- If
execution.enabled_recipesis non-empty, skip entries whose registered ID is absent from that allowlist. - Construct the Recipe with no arguments.
- Call
matches()with the plan produced so far. - When it matches, replace the current plan with the value returned by
apply()and record the Recipeidinapplied_recipes. - Continue to the next registry entry, which can inspect the updated plan.
runtime.recipe_dirs order and then each package’s RECIPE_CLASSES order. All matching Recipes are applied; the Planner does not automatically resolve overlapping writes.
BaseRecipe currently declares priority and enabled_by_default, but the Planner does not consult either attribute. Do not claim that priority changes ordering or use either attribute as an enablement mechanism. Current participation is controlled by registration, execution.enabled_recipes, and matches().
Treat registration order as current execution semantics, not as a substitute for clear ownership. Avoid Recipes that write the same fields, and do not make correctness depend on an unrelated module import order.
Validate the Integration
First confirm that the package loads and the ID is present. There is currently noagentcompass list recipe command, so inspect the run-local registry directly for an external package:
--task-concurrency 1, --max-retries 0, and DEBUG logging. Verify:
matches()is false for an unrelated Benchmark or Environment;- the expected ID appears in each relevant attempt under
resolved_execution_plansinrun_info.jsonand in the terminal result’s top-levelapplied_recipeslist; - each saved attempt’s resolved execution plan contains the intended values;
- an explicit compatible image, workspace, resource, or network value survives Recipe application;
- the selected Environment can build its config from the adjusted plan and clean up after both success and failure.
Completion Checklist
- The behavior belongs in deterministic per-task planning rather than Benchmark, Harness, Environment, or runtime mechanics.
matches()andapply()are deterministic and side-effect free.apply()returns a copied plan and preserves explicit user intent.- The Recipe ID is unique, and the correct built-in or
RECIPE_CLASSESregistration path loads it. - Matching and non-matching cases, explicit overrides, resolved plans, and cleanup have been verified.
- Public user behavior and supported combinations are documented in both languages.
