Skip to Content
AgentsRecipesRun a Recipe

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 handles

Fork 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 --pretty

Check four things:

  • The exact input keys. These are what --input must use.
  • Each input’s nodeType. A text port and an image port 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 --version if 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 240

Fork 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 \ --progress

Destinations: 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.

FormOn a written portOn an image port
plain textThe literal textNot valid
node: or asset:The referenced handleThe referenced image
@path/to/fileThe file’s text contentsThe file, uploaded as an image
@-Text from stdinStdin bytes as an image
@@valueA literal value starting with @—
clipboardLiteral textThe macOS clipboard image
gavana recipe run recipe:social-creative-angles \ --input offer-brief=@brief.md \ --destination agent-canvas \ --idempotency-key social-angles-2026-08-04

The @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-wait

Then resume with the returned handle:

gavana run wait run:RUN_ID --output markdown gavana run get run:RUN_ID --jq '.status' -r

Both 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' -r

Report 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_SECRET

The 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 text node with metadata.isList: true, metadata.listType: "prompt", and metadata.listExecutionMode: "batch" for separate outputs.
  • One checked metadata.listItems entry 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 referenceBindings as a nodeId and role pair — 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, and asset: handles for image outputs
  • A new canvas revision
  • On failure, a failure.code and a decision to hand back to the user

Common mistakes

MistakeWhat happens
Treating recipe fork as running itNothing runs. Fork is setup only — that is the point
Guessing input keysUsage error naming the unknown key. Use recipe get
@image.png on a written portThe file is inserted as text. Upload it and pass the asset: handle
Running without current-turn approvalOff contract. The user is charged for something they did not ask for
Retrying a failure automaticallyTwo charges for one intent
--destination new-canvas without --canvas-titleUsage error before anything starts
Last updated on