Recipes
What a recipe is
A recipe is the packaged, versioned behavior of your agent. It is not a copy of your code; it is a precise pointer to one exact version of it in git. Everything the agent needs to run lives there: its instructions, profiles, skills, and judges. When you point Introspection at a recipe, you are handing it one immutable snapshot of “how this agent behaves.”
Authoring recipes. Build a recipe locally with the
pi-recipes CLI (beta):
scaffold one with recipes create, validate it with recipes check, run it
with pi --recipe <name>, and publish it to git. Browse
pi.recipes for complete recipes you can inspect or
customize. Introspection pins the resulting commit as the recipe it deploys.
Inside a recipe
A recipe is a Pi package: a folder whose package.json carries a pi block declaring the recipe’s resources — agents, skills, prompts, optional extensions, and the MCP servers the recipe is allowed to reach. The system prompt lives in SYSTEM.md, and each agent is a YAML file (the default is agents/agent.yaml; named variants are just more agents).
A small customer-support recipe might look like this:
customer-support/
├── package.json
├── SYSTEM.md
├── agents/
│ ├── agent.yaml
│ ├── triage.yaml
│ └── escalation.yaml
├── skills/
│ └── ticket-triage/SKILL.md
├── judges/
│ └── useful-support-resolution.yaml
└── .introspection/
└── customer-support-agent.yamlThe files have different jobs: package.json declares what the recipe contains, the agent profile selects behavior and capabilities, a skill teaches a reusable workflow, and the .introspection manifest connects the package to a managed runtime.
package.json
{
"name": "@acme/customer-support",
"version": "0.1.0",
"description": "Triage support requests, draft grounded replies, and escalate sensitive cases.",
"type": "module",
"scripts": {
"check": "recipes check . --profile ci"
},
"pi": {
"agents": ["agents/*.yaml"],
"skills": ["skills/**/SKILL.md"],
"mcp": {
"servers": [
{
"id": "slack",
"required": false,
"tools": {
"include": ["slack_read_thread", "slack_send_message_draft"]
}
}
]
}
}
}The package-level MCP list is the recipe’s outer boundary. Each agent can narrow that list, but cannot grant itself a server or tool the package did not declare. Keeping risky actions draft-only in the instructions is useful guidance; excluding the send tool is the stronger capability boundary.
An agent YAML declares, with strict validation (unknown keys are errors, not silently ignored):
| Key | What it declares |
|---|---|
from | Another agent to inherit from. Objects (model, mcp, extensions) merge by key; lists (tools, skills, subagents) replace. |
model | The model spec: name (<provider>/<model_id>), thinking_level (off–xhigh), temperature, max_tokens, cache_retention, timeout/retry settings, and per-provider options such as OpenRouter routing or Anthropic betas. |
tools | An exact allowlist of the built-in and extension tools the agent may use — always enforced. |
mcp | Per-server MCP tool selection (see MCP). Omit it and the agent gets no MCP tools. |
skills / subagents | The skills the agent loads and the other recipe agents it may delegate to. |
system_instructions | Content applied to the system prompt in append or replace mode. |
What you write is exactly what runs: the runtime adds nothing to the prompt beyond SYSTEM.md and the agent’s system_instructions — no implicit capability notices, paths, or tool hints — and the agent’s tool and MCP selections are enforced as written, failing closed if the policy is invalid.
Immutable by design
A recipe is immutable. Once it exists, the version it points at never changes. Iterating on your agent does not edit a recipe in place; it produces a new version, recorded as a new recipe.
Only the operator-facing name and description can be edited after the fact. Pointing at different content always means creating a new recipe.
This single property is what the rest of the platform leans on:
- Every run is reproducible. A task that ran on a given recipe can be reconstructed exactly, because the behavior was pinned to a commit that can’t drift underneath you.
- Any two versions are comparable. Because each iteration is its own immutable row, you can line up version A against version B and trust that the only thing that changed is what’s in the diff.
Think of a recipe the way you think of a git commit: a fixed point you can always return to. “Editing the recipe” is a contradiction. You make a new commit, and that’s a new recipe.
How recipes are used
A recipe on its own does not run. It becomes runnable when it is bound into a runtime: the deployable version of your agent that joins a recipe to Introspection’s execution harness. One recipe can back a runtime; iterating the recipe produces a new runtime version in the same lineage.
Recipes are also referenced directly by experiments: an experiment arm names a runtime version, and through it the recipe that arm is testing. Comparing two arms is, at bottom, comparing two recipes on the same live traffic.
Related
- Runtimes: the deployable version that binds a recipe to the execution harness.
- Experiments: A/B testing between recipe-backed runtime versions.
- Judges: quality rubrics authored inside the recipe’s git repository.
- Core Concepts: how recipes fit the rest of the model.