# Template Recipe Standard

Template recipes are saved definitions around the synchronous [Command Template Standard](./command-templates.md). JSON remains the canonical precise format; Markdown is a literate authoring format that compiles into the same recipe model.

**Meta-contract:** a recipe stores a command-template graph plus defaults and run mode. It does not create a second execution language.

**Scope:** reusable JSON/Markdown shape, recipe naming, file-backed recipes, co-located recipes, recipe-layer imports/references, call-time values, foreground execution, and the `async: true` handoff to the [Async Run Standard](./async-runs.md).

---

## Reading Model

```text
command template = execution graph
recipe          = saved JSON definition
run             = one execution instance
async: true     = run through detached lifecycle
```

A recipe wraps one command-template tree. The wrapped `template` keeps the normal command-template semantics: argv splitting, placeholders, defaults, typed args, sequence, `parallel: true`, `when`, delay, retry, failure propagation, recover cleanup, and output selection.

Layer boundary: `imports`, `{ "name": "alias" }` imported-recipe nodes, `{alias.defaults.key}` references, fallback expressions, and recipe-local ternaries are recipe-loading features. They resolve before the command-template graph runs and do not extend the portable Command Template Standard. Typed imports are recipe definitions: they expose the imported recipe's command-template-shaped metadata (`template`, `args`, `defaults`, flags, and `values`), while async-run launch fields such as `async` and `retire_when` remain lifecycle configuration for starting a run, not part of the imported execution graph. Run state directories are runtime-owned and are not recipe or `register_tool` configuration; `{state_dir}` remains an injected run-local value for commands and artifact paths.

Packaged recipes are the pi-actors recipe standard library: declarative actor config components that can be imported, launched, inspected, overridden, or composed by user recipes. Treat them as stable building blocks rather than user-local policy.

## Layer Ownership

Template-recipe standard owns:

- Saved JSON definitions around one command-template graph, plus Markdown-authored recipes that compile to that shape.
- File-backed and co-located recipe shapes.
- Recipe identity through file-backed filename or co-located tool id.
- Recipe defaults, values, imports, import references, and import-node expansion.
- Ordered named artifact declarations through `artifacts`.
- Foreground-vs-detached selection through `async: true` when invoked by a recipe-aware host.

Template-recipe standard does not own:

- How command-template nodes execute internally.
- Async state files, logs, run-local transports, status, cancellation, or observability.
- Tool registry naming, button UX, package installation, or operator-specific policy.
- Domain workflows such as swarm quorum, release policy, backlog parsing, or merge policy.

A recipe can be synchronous or asynchronous:

- Omitted or false `async`: a registered tool executes the recipe in the foreground and returns normal tool output.
- `async: true`: a registered tool starts a detached async run and returns run metadata immediately.

## Minimal Shape

Synchronous recipe:

```json
{
  "template": "npm run check:docs"
}
```

Async recipe:

```json
{
  "async": true,
  "template": "review docs/spec.md"
}
```

A file-backed recipe's id comes from its filename, not a JSON `name` field. Legacy files may still contain `name`, but loaders ignore it for identity. `template` is the command-template tree. `async: true` selects detached run mode when the recipe is invoked through a registered tool.

## Markdown Authoring

Markdown recipes use `.md` files with YAML-like frontmatter for recipe metadata and one fenced executable block for the recipe/template body. Runtime behavior comes only from frontmatter plus the fenced block; surrounding prose is advisory for humans and future recipes-context use.

````markdown
---
description: Literate docs check
args:
  - scope:path
defaults:
  scope: docs
mailbox:
  accepts:
    - control.kill
---

Human notes can explain intent, examples, or review guidance.

```template
npm run check -- {scope}
```
````

Fenced blocks marked `template`, `command-template`, `json`, or `recipe` are executable. A `template` fence stores its text as the command-template string. A JSON fence can contain either a full recipe object with `template` or a raw command-template value. Frontmatter supports the recipe metadata used by JSON recipes, including `args`, `defaults`, `imports`, `mailbox`, `artifacts`, `async`, and command-template flags. For Markdown ergonomics, `args` may be either a YAML list or a comma-separated scalar, and `defaults` may be either a YAML object or a list of `key: value` entries; both normalize to the same JSON recipe shape.

JSON remains the source-of-truth format for precise machine editing. If `<id>.json` and `<id>.md` exist in the same discovery priority layer, `<id>.json` wins and the Markdown recipe is reported as shadowed.

## Discovery Priority

Recipe priority only matters when two discovered recipes have the same filename id. The conceptual ladder from lowest to highest priority is:

1. No recipe for that id.
2. Packaged pi-actors recipe components, acting as the standard library.
3. Explicitly referenced ad hoc user recipe files located outside `~/.pi/agent/recipes`.
4. User recipe files under `~/.pi/agent/recipes/*.json` or `*.md`.

The high-priority user recipe directory is also the default tool set: recipes placed there are agent tools by location. This preserves the old advantage of a tool-only registry because listing `~/.pi/agent/recipes` shows the operator-managed tool surface. Packaged and ad hoc recipes are recipe components by default; they become tools only when copied or registered into the agent recipe root.

Higher-priority files shadow lower-priority files with the same basename. Within one priority layer, same-id JSON shadows Markdown because JSON is the canonical precise format. A highest-priority invalid recipe is still visible and blocks fallback so operators do not accidentally run packaged behavior when a user override is broken. A highest-priority recipe with `disabled: true` also blocks fallback, is not launchable, and intentionally disables that id. Healthy overrides are silent; failed bare-name launches caused by invalid or disabled shadowing include compact `reason=shadowed_invalid` or `reason=shadowed_disabled` diagnostics with the active path, blocked candidate, and recipe-doctor hint.

## Usage And Lineage Metadata

User-owned recipe launches update extension-maintained lineage ledgers under `.usage/recipes/<recipe-name>.json` plus a path index. Authored recipes remain untouched. The ledger keeps lifetime and revision-local launch counts, executable fingerprints, former names and paths, bounded revision ancestry, transition events, and review epochs.

Lifetime usage survives rename, promotion, demotion, and executable revision. Revision-local counters restart when executable content changes, making the new fingerprint eligible for portfolio review without erasing prior evidence. Discovery merges current lineage evidence into inspection; packaged standard-library recipes receive no mutable usage ledger. The unreleased format stays intentionally unversioned until a public compatibility boundary exists.

There is intentionally no failure counter: a failed launch can reflect caller misuse, missing values, or environment state rather than recipe quality. Usage remains evidence, not an automatic usefulness verdict. Automatic review combines it with contract quality, portability, duplication, safety, and likely future value. `register_tool draft=...` is the preferred fenced single-draft override; a deliberate move/copy from `drafts/` into the recipe root also remains valid, though it may defer an already captured automatic batch.

For object form, keep `template` last. Recipe metadata comes first; executable content stays last.

## Named Artifacts

Use recipe-level `artifacts` to declare stable artifact names and paths for the whole recipe, ordered from most important to least important:

```json
{
  "args": ["report_path:path"],
  "defaults": { "report_path": "artifacts/report.md" },
  "artifacts": {
    "report": "{report_path}",
    "summary": "artifacts/summary.json"
  },
  "template": "generate-report --out {report_path}"
}
```

`output` and `artifacts` are intentionally different. `output` is the command-template primary result selector and defaults to stdout; it participates in sequence/stdin flow. `artifacts` is recipe metadata: an ordered named artifact manifest for humans, async completion messages, and downstream tooling. `stdout` remains the default command result channel and is not renamed by `artifacts`.

## Mailbox

Use recipe-level `mailbox` to document the semantic messages a recipe actor accepts and emits:

```json
{
  "mailbox": {
    "accepts": [
      "control.continue",
      "control.revise",
      "control.approve",
      "control.kill"
    ],
    "emits": ["checkpoint.needs_scope", "branch.done", "run.done"]
  }
}
```

`mailbox` is contract metadata, not transport configuration. It should name semantic message types, not transport commands, file paths, or CLI fragments. Entries may be strings or typed objects such as `{ "type": "task.assign", "requires_response": true, "summary": "Assign work" }`; inspection normalizes both forms. Acceptance contracts are advisory by default: messages outside `mailbox.accepts` produce warnings rather than hard routing failures.

## Actor Recipe Context

File-backed async recipes automatically build a bounded recipe context bundle for child LLM actor launches. The bundle is appended to child `pi -p` prompts as JSONL: each line is one recipe/context record containing filename-derived `name`, source file, role/depth, import path/alias, and the raw authored recipe JSON. The record whose command-template node launched the current child is marked with `"you_are_here": true` and path metadata.

This context is provenance, not the task instruction. The authored prompt remains authoritative; the bundle explains the recipe/composition tree that produced the launch. A child actor can use it to give advisory feedback on whether its recipe, imports, mailbox metadata, and role boundaries fit the task, without needing a separate hand-written workflow explanation. Recipes that require a minimal child prompt may opt out:

```json
{
  "async": true,
  "actor_context": false,
  "template": "pi -p --model {model} {prompt}"
}
```

`"actor_context": "off"` is equivalent. Bundle generation uses the same recipe file-size and import-depth safety limits as normal recipe loading.

## Actor Message Delivery

Recipes do not declare a second event-delivery policy. A running actor emits addressed messages such as `command.done`, `run.done`, or `checkpoint.needs_input`; the coordinator/runtime decides whether a message stays diagnostic, becomes a notification, or re-enters the agent context. This keeps recipe metadata focused on the actor contract:

```json
{
  "mailbox": {
    "accepts": ["control.kill"],
    "emits": ["command.done", "run.done", "run.failed"]
  },
  "template": "run-subtask {prompt}"
}
```

## Command-Template Flags At Recipe Top Level

Top-level command-template flags may sit beside recipe metadata such as `async`:

```json
{
  "async": true,
  "parallel": true,
  "timeout": 300000,
  "failure": "branch",
  "template": ["review-a docs/spec.md", "review-b docs/spec.md"]
}
```

Valid command-template flags include `args`, `defaults`, `parallel`, `concurrency`, `min_successful`, `when`, `label`, `timeout`, `delay`, `output`, `retry`, `failure`, `recover`, and `repeat`.

Timeout is disabled by default. Set a positive `timeout` when a recipe should fail closed after a bounded runtime; omit it, or set `0`, for intentionally open-ended runs that will be stopped by async cancellation, such as background audio playback.

## Valid Graph

The valid chain is:

```text
tool → template reference → recipe → run → template
```

A recipe must define `template` directly. Tool exposure comes from where the recipe is stored, so the same recipe remains transportable across user, ad hoc, and packaged roots.

A recipe may live in a file or be co-located inside a registered tool entry. Both are storage variants of the same graph.

## File-Backed Recipes

Reusable local recipes live in:

```text
~/.pi/agent/recipes/*.json
~/.pi/agent/recipes/*.md
```

Bare recipe names resolve under that directory, so `file: "review-docs"` loads:

```text
~/.pi/agent/recipes/review-docs.json
# or, when no same-id JSON file exists:
~/.pi/agent/recipes/review-docs.md
```

Call-time params override file params. `values` are merged with file values; call-time values win. If a run id is omitted for an explicit async start, the file basename becomes the default run id.

## Registered Recipe Tools

A registered tool is a recipe file exposed as an agent tool. User recipes under `~/.pi/agent/recipes/*.json` or `*.md` are tools by location; packaged/ad hoc recipes are components unless copied or registered into that user recipe root:

```json
{
  "description": "Start an async docs review actor",
  "async": true,
  "args": ["scope:path", "model:string"],
  "template": "review {scope} --model {model}"
}
```

If a tool recipe contains `async: true`, calling the tool starts a detached actor run and returns metadata. If `async` is omitted or false, calling the tool executes the recipe foreground and returns normal tool output.

## Values And Public Args

Recipe placeholders come from runtime values, recipe `defaults`, inline placeholder defaults, and registered-tool defaults. Pi tool launches also inject `{current_model}` and `{current_thinking}` when the active session exposes a selected model and thinking level; recipes that require those placeholders fail before fanout when the current value is unavailable unless the caller supplies an explicit override such as `model` or `thinking`. Async runs persist `model_policy` provenance in run status, progress, and terminal results so operators can tell whether model/thinking values were inherited, explicit, mixed, or unresolved.

Recipe tools derive public arguments from the referenced or co-located command template when the recipe is available locally. Explicit `args` is still available when the public tool surface should be narrower than the recipe internals.

Example: a recipe may expose a private `repo` default for an example script, while the registered public tool only asks for `file`, `volume`, and `player`.

## Recipe Imports

File-backed recipes may import other file-backed recipes at the recipe layer. Imports are resolved before the command-template graph is executed, so command-template core stays registry-free and synchronous. Recipe loading is intentionally bounded: a single recipe file larger than 1 MiB is rejected before JSON parsing, and import chains deeper than 32 are rejected before further resolution. Split very large prompts/data into explicit files or artifacts and keep recipe graphs shallow enough for operator review.

```json
{
  "name": "parent",
  "imports": {
    "prepare": "prepare-worktree.json",
    "test": {
      "from": "run-tests.json",
      "values": { "suite": "unit" }
    }
  },
  "template": [{ "name": "prepare" }, { "name": "test" }]
}
```

An import binding may be either a string recipe path/name or an object with:

- `from`: recipe path or bare recipe name. Import paths support static load-time placeholders: `{repo}` expands to the directory above the active recipe root, and `{agent}` expands to the pi agent directory. For a packaged recipe in `<repo>/recipes/name.json`, `{repo}/recipes/other.json` resolves to a sibling packaged recipe. For a user recipe in `~/.pi/agent/recipes/name.json`, `{repo}` and `{agent}` both resolve to `~/.pi/agent`. Bare names such as `utility-package-summary` resolve by recipe priority: first `~/.pi/agent/recipes`, then the importing recipe's directory, then the packaged standard library. This makes packaged recipes easy to reuse while preserving user/ad hoc overrides by filename identity.
- `defaults`: extra default values exposed through the import.
- `values`: explicit values for embedding that imported recipe.

A template node of `{ "name": "alias" }` is replaced with the imported recipe's command-template graph. Imported recipe defaults are merged with import `defaults`, import `values`, node `defaults`, and node `values`; later layers win. This lets a parent recipe embed a reusable recipe in a sequence or `parallel: true` branch without inventing a workflow language.

Use imports as the default adapter for composed ready recipes. A user-root recipe such as `~/.pi/agent/recipes/repo_context_check.json` can import the maintained source recipe and call `{ "name": "alias" }` instead of duplicating the source recipe's script command. Skill scripts are the strongest version of this rule: when a skill provides `recipes/<name>.json` for its `scripts/*` entrypoint, local tools should delegate to or import the skill recipe instead of invoking the script path directly.

## Direct Recipe Delegation

A `template` string that resolves to a recipe name or recipe file path delegates to that recipe instead of executing the recipe file as a binary:

```json
{
  "description": "Play music through the packaged player recipe.",
  "async": true,
  "defaults": { "source": "~/Music", "volume": "70" },
  "template": "music-player"
}
```

Delegation is a one-to-one handoff for thin wrappers and simple reuse. It uses the same bare-name priority as imports: user-root recipes under `~/.pi/agent/recipes`, then the importing recipe's directory, then packaged standard-library recipes. A higher-priority invalid or disabled recipe still blocks lower-priority fallback so operator overrides fail closed.

Delegated recipes remain the source of truth for their command template, async setting, args/defaults, mailbox, artifacts, and future fixes. Wrapper recipe fields may narrow args/defaults or override lifecycle metadata. Use imports plus `{ "name": "alias" }` when you need rich composition, multiple recipe nodes, import-specific values/defaults, or a pipeline where the reusable recipe is only one step.

Async composition stays explicit: importing or delegating to a recipe reuses its command-template-shaped definition. It does not start a nested async run. Put `async: true` on the parent recipe when the combined imported graph should run detached as one run with one state dir; thin delegation may inherit `async: true` from the delegated recipe. Ephemeral coordinator recipes may declare `retire_when: "children_terminal"` as an opt-in lifecycle hint for future graceful retirement handling; persistent services and implementer loops should omit it. For agent-callable fanout, prefer public inputs such as `prompts:array` plus `repeat: "{prompts.length}"`, then select each branch value with `{prompts[index]}` instead of baking concrete prompts or file names into the reusable recipe.

```json
{
  "async": true,
  "imports": {
    "review": "review-one.json"
  },
  "parallel": true,
  "failure": "branch",
  "template": [
    { "name": "review", "values": { "scope": "README.md" } },
    { "name": "review", "values": { "scope": "docs/template-recipes.md" } }
  ]
}
```

Recipes can also read imported metadata and value containers before command-template placeholder expansion. Each import alias acts like a recipe-local variable:

```json
{
  "imports": {
    "base": {
      "from": "base.json",
      "values": { "target": "docs" }
    }
  },
  "defaults": {
    "profile": "{base.defaults.profile=safe}",
    "target": "{base.values.target}",
    "label": "{base.name}:{base.values.target}",
    "enabled_label": "{base.defaults.enabled?enabled:disabled}"
  },
  "template": "run {base.defaults.profile=safe} {base.values.target} {label}"
}
```

Supported references are:

- `{alias.name}`
- `{alias.file}`
- `{alias.defaults.key}`
- `{alias.values.key}`
- `{alias.defaults.key=fallback}` for a missing/null import value fallback.
- `{alias.values.key?truthy:falsy}` for a small recipe-layer ternary.

Nested object keys are dot-separated. Import references are resolved before normal command-template placeholders, so ordinary values such as `{label}` still flow through command-template defaults and call-time values. Ternaries use simple falsy checks for missing, null, false, zero, and empty string. Missing imports, missing values without fallback, and import cycles fail during recipe loading.

## Recipe Shape

Use the filename for file-backed recipe ids, and use `async: true` for detached runs. Use `parallel: true` for fanout, `when` for node guards, and semantic public args such as `tools`, `all`, or `timeout_ms` instead of leaking CLI fragments or reusing node-control names. Local files belong under `~/.pi/agent/recipes/*.json` or `*.md` before relying on recipe launchers.

If a proposed recipe needs a scheduler, queue daemon, `goto`, or custom workflow syntax, stop. Keep the recipe as saved command-template JSON and put policy in the registered tool, script, or caller.
