Guild can run Goose recipes 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:
- 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.
- 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.
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.
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.
The input schema persisted on the version is a JSON Schema (Draft 2020-12) describing this shape:
{ "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.
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.
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
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:
{
"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:
{
"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:
- Parameter values — declared defaults overlaid with
input.parameters.
- Rendering —
instructions and prompt are rendered with the parameter values.
- System prompt — the rendered
instructions, when present.
- 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.
- 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-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.