> ## Documentation Index
> Fetch the complete documentation index at: https://docs.guild.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Goose recipes

> Run a Goose recipe as a Guild agent without writing SDK code.

Guild can run [Goose recipes](https://block.github.io/goose/docs/guides/recipes/recipe-reference/) as agents. You supply a `recipe.yaml` file, and the platform validates it at build time, derives typed input and output schemas from it, and executes it at run time.

This page describes every rule the platform applies: which fields are honored, how recipes are validated, how input and output schemas are constructed, and how the recipe and caller input combine at task start.

## Recipe file

The agent version's files must include a `recipe.yaml` at the root. The file must be valid YAML whose document root is a mapping. Other files may exist alongside `recipe.yaml`; the platform does not consume them. JSON recipes (`recipe.json`) are not supported.

## Lifecycle

The same `recipe.yaml` is parsed at two points:

1. **Build time** — The recipe is fully validated. On success, `description`, `input_schema`, and `output_schema` are extracted and persisted on the agent version. Any validation error fails the build, and all errors are reported together.
2. **Task start** — The recipe is re-parsed from the version's files. `instructions`, `prompt`, and `parameters` are not persisted — the recipe file is their single source of truth. Because versions are immutable, a recipe that validated at build time parses identically at task start.

## Recipe fields

Every top-level field is honored, ignored, or rejected.

| Field          | Treatment                                                                  |
| -------------- | -------------------------------------------------------------------------- |
| `description`  | **Honored** — persisted as the version and agent description.              |
| `instructions` | **Honored** — rendered and sent as the system prompt.                      |
| `prompt`       | **Honored** — rendered and used as the default initial message.            |
| `parameters`   | **Honored** — drives the input schema and template rendering.              |
| `response`     | **Honored** — `json_schema` becomes the output schema.                     |
| `title`        | **Ignored** — validated as a string if present; Guild uses the agent name. |
| `version`      | **Ignored**                                                                |
| `author`       | **Ignored**                                                                |
| `activities`   | **Ignored** — desktop-only in Goose; the Goose CLI ignores them too.       |
| `extensions`   | **Rejected** — the platform does not honor recipe extensions.              |
| `settings`     | **Rejected** — provider and model are platform-controlled.                 |
| `sub_recipes`  | **Rejected** — not supported.                                              |
| `retry`        | **Rejected** — not supported.                                              |
| Any other key  | **Rejected** — catches typos and future Goose fields not yet reviewed.     |

At least one of `instructions` or `prompt` must be present.

<Warning>
  Rejected fields produce a build error naming the field — for example, `Field 'extensions' is not supported on Guild`. This is intentional: a recipe that declares an extension expects the agent to have it, and running without it produces confusing behavior. The build is the earliest moment authors learn what the platform will not support.
</Warning>

## Validation

### Recipe-level

* `description` is required and must be a non-empty string.
* `instructions`, `prompt`, and `title` must be strings when present.
* At least one of `instructions` or `prompt` must be present.
* Top-level fields not listed in the table above are rejected.

### Parameter-level

`parameters`, when present, is a list of mappings. Each parameter must satisfy:

| Field         | Rule                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------ |
| `key`         | Required. Matches `^[a-zA-Z_][a-zA-Z0-9_]*$`. Must be unique across the list.              |
| `input_type`  | Required. One of `string`, `number`, `boolean`, `date`, `select`. `file` is rejected.      |
| `requirement` | Required. One of `required`, `optional`, `user_prompt`.                                    |
| `description` | Required. Non-empty string.                                                                |
| `default`     | Required for `optional` parameters; forbidden for `required` and `user_prompt` parameters. |
| `options`     | Required non-empty list of unique strings for `select`; forbidden for all other types.     |

**Default coercion:** any YAML scalar `default` is accepted and coerced to the declared type. Accepted values by type:

* `number` — integer, float, or numeric string.
* `boolean` — YAML boolean or `"true"` / `"false"` (case-insensitive).
* `date` — ISO `YYYY-MM-DD` string.
* `select` — must be one of the declared `options`.
* `string` — any scalar, kept as a string.

A non-coercible default is a build error. The coerced value is what appears in the generated JSON schema.

### Template-level

`instructions` and `prompt` are Jinja-style templates.

* Templates must parse without syntax errors.
* Every variable referenced in `instructions` or `prompt` must be a declared parameter.
* Every declared parameter must be referenced in `instructions` or `prompt`. (`activities` are ignored on Guild and do not count as usage.)
* The built-in `{{ recipe_dir }}` variable is rejected — there is no recipe directory in the Guild execution model.
* Rendering is sandboxed: template expressions cannot access host internals.

### Response-level

* `response`, when present, must be a mapping containing `json_schema`.
* `response.json_schema` must be a mapping and a structurally valid JSON Schema (Draft 2020-12).
* `response` without `json_schema` is a build error.

## Input schema

The input schema persisted on the version is a JSON Schema (Draft 2020-12) describing this shape:

```json theme={null}
{ "text": "...", "parameters": { "<key>": "<value>", ... } }
```

The schema has `type: object` and `additionalProperties: false`. It always contains a `text` property. It contains a `parameters` property only when the recipe declares at least one parameter.

### `text`

`text` is always present with `type: string`.

* **Required** when the recipe has no `prompt` — the caller must supply the initial message. Description: `"The message for the agent."`
* **Optional** when the recipe has a `prompt`. Description: `"Optional message appended after the recipe's default prompt."`

### Parameters

The `parameters` property has `type: object` and `additionalProperties: false`. Its `required` array lists every parameter whose `requirement` is `required` or `user_prompt`.

<Note>
  `user_prompt`-requirement parameters are treated as required. Goose's semantics ("interactively prompt the user if not provided") have no headless equivalent — leaving them unset would surface literal `{{ key }}` text in the rendered prompt. On Guild, the input form is the user prompting.
</Note>

`parameters` itself is required at the top level when its `required` array is non-empty.

Each parameter maps to a JSON Schema property according to its `input_type`:

| `input_type` | JSON Schema property                      |
| ------------ | ----------------------------------------- |
| `string`     | `{"type": "string"}`                      |
| `number`     | `{"type": "number"}`                      |
| `boolean`    | `{"type": "boolean"}`                     |
| `date`       | `{"type": "string", "format": "date"}`    |
| `select`     | `{"type": "string", "enum": [<options>]}` |

Each property carries the parameter's `description`. Optional parameters also carry their coerced `default`.

### Example

```yaml theme={null}
description: Review code with a configurable focus
instructions: You are a {{ language }} reviewer focused on {{ focus }}.
prompt: Review the code provided below.
parameters:
  - key: language
    input_type: select
    requirement: required
    description: Language under review
    options: [python, typescript]
  - key: focus
    input_type: string
    requirement: optional
    default: best practices
    description: Review focus area
```

This recipe produces the following input schema:

```json theme={null}
{
  "type": "object",
  "properties": {
    "text": {
      "type": "string",
      "description": "Optional message appended after the recipe's default prompt."
    },
    "parameters": {
      "type": "object",
      "properties": {
        "language": {
          "type": "string",
          "enum": ["python", "typescript"],
          "description": "Language under review"
        },
        "focus": {
          "type": "string",
          "default": "best practices",
          "description": "Review focus area"
        }
      },
      "required": ["language"],
      "additionalProperties": false
    }
  },
  "required": ["parameters"],
  "additionalProperties": false
}
```

If the recipe omitted `prompt`, `required` at the top level would be `["text", "parameters"]`.

## Output schema

When `response.json_schema` is present, it is persisted verbatim as the version's output schema.

When absent, the output schema defaults to the canonical text shape, so the agent's final answer posts to chat:

```json theme={null}
{
  "type": "object",
  "properties": {
    "type": {"type": "string", "const": "text"},
    "text": {"type": "string"}
  },
  "required": ["type", "text"],
  "additionalProperties": false
}
```

## Runtime semantics

At task start, the parsed recipe and the validated input combine as follows:

1. **Parameter values** — declared defaults overlaid with `input.parameters`.
2. **Rendering** — `instructions` and `prompt` are rendered with the parameter values.
3. **System prompt** — the rendered `instructions`, when present.
4. **Initial message** — determined by whether the recipe has a `prompt` and whether the caller supplied `text`:
   * Recipe has no `prompt` → `text` (the schema makes it required).
   * Recipe has a `prompt`, no `text` given → the rendered prompt.
   * Both present → the rendered prompt, then `text`, joined by a blank line.
5. **Workspace context** — when present, prepended to the rendered `prompt` at task start, wrapped in a `{% raw %}` block so it is not treated as a template. The resulting `prompt` orders workspace context first, then the rendered recipe prompt, then `text`. It is never injected into the rendered `instructions`. Prepending the context inside the `prompt` carries it on every `--resume` turn, matching the native Goose runtime behavior.

The append rule mirrors Goose's own behavior: in interactive mode the recipe prompt is the first turn, with the user's messages following it. Append preserves that ordering — default behavior first, user steering after.

### Chat input mediation

Chat-originated input arrives as canonical text `{"type": "text", "text": "..."}`. When the target schema has a top-level `text` string property, that input maps to `{"text": "<text>"}`. Validation then applies as usual — a bare chat message can only start a Goose agent whose schema does not require `parameters`.
