Run a Recipe
A Recipe is a reusable canvas workflow with typed inputs and declared outputs. Finding, inspecting, and forking one is free. Running one is paid work.
Every Recipe start currently requires image:generate and its dependent scopes — including text-only Recipes. Treat every recipe run as a paid action requiring explicit current-turn approval.
Scopes. recipe search and recipe get: canvas:read. recipe fork: canvas:read, canvas:write. recipe run: canvas:read, canvas:write, asset:read, image:generate — plus job:manage to wait for the result.
Call sequence
recipe search → recipe get → [approval] → recipe run → run wait → report handlesFork is a separate, optional branch that never runs anything:
recipe search → recipe get → recipe fork → (a private editable card on the canvas)Steps
Find the Recipe
gavana recipe search "product visual" --jq '.recipes[].handle' -r--limit is 1–100; continue with --cursor from page.nextCursor.
Recipes are also browsable in the product: Canvas → Add → Browse Recipes. That surface adds one compact Recipe workflow to the canvas and never starts image work automatically.
Check. One exact recipe: handle. If two match the brief, ask.
Inspect it before running it
gavana recipe get recipe:product-visual-direction --prettyCheck four things:
- The exact input keys. These are what
--inputmust use. - Each input’s
nodeType. Atextport and animageport interpret the same value differently. - The declared outputs. This is what will be materialised on the destination canvas.
- Whether the Recipe is versioned. Pin it with
--versionif you need reproducibility.
Optionally fork it first
Forking adds a private, editable copy of the Recipe card to a canvas. It is setup only and never executes:
gavana recipe fork recipe:product-visual-direction \
--canvas canvas:OWNER_UID:CANVAS_ID \
--idempotency-key fork-product-visual-a \
--x 1600 --y 240Fork when the user wants to adjust the workflow before running it. Skip it when they want to run the catalog Recipe as-is.
Get explicit approval
Not before this step. recipe fork needed none; recipe run needs it now, in the current turn.
State plainly: which Recipe, which destination canvas, what it will produce, and that it uses the connected AI provider. Then wait.
Run it
gavana recipe run recipe:product-visual-direction \
--input product-context="A matte black travel bottle for a quiet premium campaign" \
--destination canvas:OWNER_UID:CANVAS_ID \
--idempotency-key brief-2026-08-04-a \
--progressDestinations: agent-canvas, new-canvas (requires --canvas-title), or an existing canvas: handle.
Input keys. Use the keys recipe get returned. The CLI also accepts a port’s node id, its normalised label, and — where unambiguous — the first word of its label. Supplying the same input twice is an error, and every declared input needs a value unless you are running a connected private instance with --instance-node.
Input values.
| Form | On a written port | On an image port |
|---|---|---|
plain text | The literal text | Not valid |
node: or asset: | The referenced handle | The referenced image |
@path/to/file | The file’s text contents | The file, uploaded as an image |
@- | Text from stdin | Stdin bytes as an image |
@@value | A literal value starting with @ | — |
clipboard | Literal text | The macOS clipboard image |
gavana recipe run recipe:social-creative-angles \
--input offer-brief=@brief.md \
--destination agent-canvas \
--idempotency-key social-angles-2026-08-04The @path rule is the one that catches people. On a written port @brief.md inserts the file’s text. To use a local image on a written port that accepts “an image or written note”, upload it first with gavana asset upload and pass the returned asset: handle.
Optional overrides: --version, --model, --size, --quality, --base-revision.
Wait, or hand off
By default the CLI waits up to 15 minutes, polling every 1.5 seconds. --progress streams state transitions to stderr as JSON while stdout stays clean.
To hand off instead:
gavana recipe run recipe:RECIPE_ID \
--input product-context="..." \
--destination agent-canvas \
--idempotency-key brief-2026-08-04-a \
--no-waitThen resume with the returned handle:
gavana run wait run:RUN_ID --output markdown
gavana run get run:RUN_ID --jq '.status' -rBoth require job:manage. Without that scope, use --no-wait plus a signed webhook — see below.
Read the terminal state
The terminal Run carries typed outputs with durable node: handles and, for images, asset: handles, plus estimatedSeconds and observed queue and execution timing.
On failure it carries a stable failure.code and failure.retryable, and the CLI exits 9. A failed provider request is returned as a terminal Run result rather than a generic request error, so the reason is readable rather than buried.
Do not retry automatically. failure.retryable: true is information for the user’s decision. Report the terminal state and ask. If they say yes, that is a new intent — use a new idempotency key.
Verify and report
gavana canvas get canvas:OWNER_UID:CANVAS_ID --jq '.canvas.revision' -rReport the durable node: and asset: handles. The run: handle is a temporary observation record — Recipe Runs currently persist without the short image/Action TTL, but the durable handles are still what you hand over.
Handing off with a webhook
For a start-only token without job:manage, or a process that will exit before the Run finishes:
read -rs 'GAVANA_WEBHOOK_SECRET?Webhook signing secret: '; printf '\n'
export GAVANA_WEBHOOK_SECRET
gavana recipe run recipe:RECIPE_ID \
--input product-context="..." \
--destination agent-canvas \
--idempotency-key brief-2026-08-04-a \
--webhook-url 'https://automation.example.com/hooks/gavana' \
--no-wait
unset GAVANA_WEBHOOK_SECRETThe secret must be 32–512 characters and comes from GAVANA_WEBHOOK_SECRET unless you name another variable with --webhook-secret-env. The CLI never prints it. Gavana’s worker keeps the Run moving after your process exits, and the callback carries the typed terminal outputs.
The token stays revocable throughout. If it is revoked or expires mid-Run, Gavana stops the Recipe with delegation_revoked and sends its signed failed callback.
Webhooks are deliberately unavailable through MCP so a signing secret cannot enter model-visible tool arguments.
Prompt Lists for product variants
When one product needs several distinct creative directions, do not run the Recipe several times with different prompts. Use a native Prompt List:
- One
textnode withmetadata.isList: true,metadata.listType: "prompt", andmetadata.listExecutionMode: "batch"for separate outputs. - One checked
metadata.listItemsentry per direction. - Connect the shared product image to the List once, and connect the List to one empty image generator.
- For a style that belongs to a single row, put the exact image in that row’s
referenceBindingsas anodeIdandrolepair — do not paste a filename into the prompt prose as a substitute.
Every product-variant row should say explicitly that the exact connected product must be preserved and never substituted. Building the List and generator prepares the workflow only; it does not start generation.
What you end up with
- A terminal Run with typed outputs
- Durable
node:handles, andasset:handles for image outputs - A new canvas revision
- On failure, a
failure.codeand a decision to hand back to the user
Common mistakes
| Mistake | What happens |
|---|---|
Treating recipe fork as running it | Nothing runs. Fork is setup only — that is the point |
| Guessing input keys | Usage error naming the unknown key. Use recipe get |
@image.png on a written port | The file is inserted as text. Upload it and pass the asset: handle |
| Running without current-turn approval | Off contract. The user is charged for something they did not ask for |
| Retrying a failure automatically | Two charges for one intent |
--destination new-canvas without --canvas-title | Usage error before anything starts |