kind: Recipe
What it is
ARecipe is a server-curated 1-click bundle (crew + MCP servers + credentials) that ships baked into the Crewship binary under internal/recipes. The manifest can request that a recipe be installed into the current workspace, but it cannot create, edit, or define new recipes — authoring belongs in server source code, not in user YAML.
This makes Recipe an install-only reference kind, in contrast to the full-CRUD kinds (Project, Label, Milestone, etc.). The distinction matters when you’re writing manifests:
- Full-CRUD kinds let the manifest describe shape and trigger creation/updates/deletion of the entity itself.
- Reference kinds only express intent (“this recipe should be installed in this workspace”); the underlying entity is owned by the server.
metadata.slug, which must match a recipe slug from the server catalog (GET /api/v1/recipes). Installs are not declarative in the same way as Projects — the server may also create credentials, crews, and MCP servers as side-effects of POST /api/v1/recipes/{slug}/install, and those side-effects appear as their own rows once installed.
Heads-up on uninstall. The backend currently exposes no DELETE endpoint for installed recipes. The manifest still acceptsinstall: falseso the YAML shape stays symmetrical, but on encountering an already-installed recipe withinstall: falsethe planner emits anUnchangedplan item carrying a warning description (recipe uninstall not supported by server) rather than aDelete. Future server versions may add a DELETE verb, at which point this kind upgrades without a YAML schema change.
YAML schema
Field reference
| Field | Required | Type | Notes |
|---|---|---|---|
apiVersion | yes | string | Always crewship/v1. |
kind | yes | string | Always Recipe. |
metadata.name | no | string | Informational. The server identifies recipes by slug, not by name. |
metadata.slug | yes | string | Must match a recipe in the server catalog (GET /api/v1/recipes). |
metadata.description | no | string | Informational only; not forwarded to the server. |
spec.install | yes | bool | true = ensure installed. false = ensure uninstalled (best-effort; see Apply behavior). |
spec.inputs | no | map[string]any | Passthrough body for the install endpoint. Keys depend on the backend’s installRecipeRequest shape; the manifest does not validate them beyond crew_slug. |
spec.inputs.crew_slug | no | string | If present, the slug must reference a declared or remote Crew. |
Examples
Minimal example — install with no extra inputs
Realistic example — install with credential values and a target crew
Uninstall intent — recorded but not executed today
Cross-kind references
A recipe install often pairs with a crew defined elsewhere in the same bundle. Apply orders Phase 2 (Crews + agents) before Phase 9 (Recipes), so the crew slug referenced ininputs.crew_slug will exist on the server by the time the recipe plan runs.
CLI reference
Recipe is driven by the generic crewship apply / crewship export commands; there is no per-kind crewship recipe … subcommand today. The endpoints below back the manifest plan/apply machinery; raw HTTP callers can hit them directly.
| Command | One-liner |
|---|---|
crewship apply --file <file> | Declarative apply — installs every Recipe doc whose spec.install: true is not yet realised. |
crewship apply --file <file> --dry-run | Show the install plan without mutating. |
crewship export workspace | Emits one Recipe doc per installed recipe (see “Round-trip via export”). |
REST endpoint mapping
| Manifest field | HTTP verb + path | Request body |
|---|---|---|
spec.install: true (not installed) | POST /api/v1/recipes/{slug}/install | spec.inputs forwarded verbatim |
spec.install: true (already installed) | — | No request issued; plan is Unchanged |
spec.install: false (installed) | — | No request issued; plan is Unchanged with warning |
spec.install: false (not installed) | — | No request issued; plan is Unchanged |
| Drift detection | GET /api/v1/recipes/{slug} | The installed JSON key on the response controls Plan branch |
| Export | GET /api/v1/recipes | Only entries with installed: true are emitted |
spec.install; “installed” is a derived state on the server (typically: “a crew matching the recipe’s crew_slug exists in the workspace and has the recipe’s MCP servers”). The manifest treats the boolean as opaque and trusts the server’s answer.
Validation rules
Validate (offline, no HTTP) enforces:
metadata.slugis non-empty.spec.inputs.crew_slug, when present, must be a string.spec.inputs.crew_slug, when present and non-empty, must reference a crew that’s either declared earlier in the bundle or already on the server.
metadata.slug actually exists in GET /api/v1/recipes) is not checked at validate time — the WorkspaceContext doesn’t carry remote recipes today, so the check is deferred to Plan, which has live HTTP access. A missing slug surfaces as a 404 from LookupRecipeRemote → a transparent “recipe not found” plan error.
Apply behavior
Default (ApplyUpsert)
Plan decision table:
spec.install | remote installed? | Action | Notes |
|---|---|---|---|
true | no | Create | POST /api/v1/recipes/{slug}/install with spec.inputs |
true | yes | Unchanged | No HTTP traffic |
false | yes | Unchanged | Warning surfaced via description (no DELETE endpoint) |
false | no | Unchanged | Benign no-op |
ApplyStrict
Strict mode adds no extra constraints for this kind — a recipe already installed is not an error (recipes are workspace-scoped catalog entries, not user-named entities, so re-declaring an installed one is a normal Unchanged outcome).
ApplyReplace
Replace mode is also a no-op for this kind today: the server has no DELETE endpoint, so even “destructively recreate” cannot uninstall the existing bundle. The planner emits Unchanged with the same warning as install: false against an installed recipe. When the backend grows a DELETE verb this kind will start emitting Delete + Create pairs in Replace mode without any YAML schema change.
Round-trip via export
crewship export workspace walks GET /api/v1/recipes and emits one RecipeDocument per row whose installed field is true. The emitted document carries:
metadata.nameandmetadata.slug= the catalog slugspec.install: true- No
spec.inputs(the install body is not persisted server-side; only the resulting crew/credentials/MCP rows are)
Unchanged) because the recipe is already installed.
Limitation. At time of writing the catalog list endpoint does not surface theinstalledflag for every row; the helper decodes whatever JSON the server returns and filters byinstalled == true. Until the backend exposes per-row installed state,ExportRecipesreturns an empty slice in practice. This is correct behaviour (“nothing to round-trip yet”) and upgrades silently once the backend ships the field.
See also
docs/manifest/crew_template.md— the deploy-only sibling for crew templates.docs/manifest/connector.md— the install-only reference kind for integration connectors.internal/recipes/recipes.go— the in-binary catalog source of truth.internal/api/recipes.go— backend handler forGET /api/v1/recipes,GET /api/v1/recipes/{slug},POST /api/v1/recipes/{slug}/install.