Skip to Content
AgentsThe safety contract

The Safety Contract

Gavana hands every connected agent the same operating contract. It ships inside the product as a versioned guide corpus — an MCP client can fetch it at runtime with guide_search and guide_get, or read it as MCP resources under gavana://guides/canvas/v1/. This page is the human-readable form of that corpus, plus the mechanism behind each rule.

Rules that are only memorised get dropped under pressure. Rules whose mechanism you understand survive the task you did not anticipate. So each section below says what the rule is, what actually happens in the system if you break it, and what compliance looks like.

The contract is not advice. Several of its rules are enforced by the service — a write against a stale revision is rejected, a destructive batch without confirmation is refused by the CLI, and a reused idempotency key with a changed payload produces a conflict rather than duplicate work. The rest are enforced by review: every change an agent makes is attributed to the token that made it.

The eight rules

#RuleEnforced by
1Read before you writeService (baseRevision) and review
2Explicit current-turn approval before paid workReview, and the shape of the tools
3Never auto-retry a paid requestReview
4Credentials never enter chat, URLs, logs, screenshots, or repositoriesYou, and the CLI’s stdin login path
5Share only X-Request-ID with supportYou
6Use only handles the service returnedService (invalid handles are rejected)
7Reuse a caller-stable idempotency keyService (deduplication and conflicts)
8Read baseRevision before a raw canvas writeService (409 on conflict)

Rule 1 — Read before you write

The rule. Before reasoning about or changing an existing canvas, read it. In MCP that is canvas_get. On the CLI it is gavana canvas get canvas:OWNER_UID:CANVAS_ID. Identify exactly one canvas handle; never pick between candidates by guessing.

The mechanism. A canvas is a live graph that a human may be editing in the browser at the same moment. Your write is expressed as a batch of operations against a revision. Without a read you do not know the current revision, you do not know which node IDs exist, and you do not know what the user has already built. Writes composed from imagination fail in one of two ways: they are rejected outright as invalid handles, or — worse — they succeed and quietly bury existing work under new nodes.

Reading also gives you the object grammar you need. Gavana canvases distinguish sticky notes (one short observation, body in metadata.content), text nodes (a paragraph, prompt, or brief, body in metadata.content), and Sections (a text node carrying metadata.isSection: true, title in title, used as a labelled spatial container). A stretched text node imitating a heading is a documented anti-pattern that validation flags as fake_section_header.

Compliance looks like. One canvas_get, then a plan expressed in the exact handles that read returned, then a write. For spatial, multi-node, or destructive changes, run canvas_validate with the proposed operations before applying them, and again afterwards.

Rule 2 — Explicit current-turn approval before paid work

The rule. Image generation, video generation, Recipe runs, and any other provider-backed execution start only when the user’s current message explicitly asks for it. Building, preparing, setting up, connecting, drafting, and making ready authorise graph edits only.

The mechanism. Gavana routes generation to whatever AI provider the user connected to their own account. That connection has real billing attached to it. There is no sandbox tier that silently absorbs an accidental run. The product’s own guide corpus states the boundary in one line: “Building or preparing a workflow does not mean running it.”

The tool surface is deliberately shaped to make the boundary visible rather than to rely on your restraint:

  • recipe fork adds a private editable Recipe card and never executes it. recipe run is the separate, explicit execution step.
  • In hosted MCP, create_canvas_workflow saves a workflow; run_canvas_workflow runs it.
  • Deterministic Image Actions — resize, crop, aspect-ratio change, side-by-side composite, add text, overlay image, colour grade, rotate — do not spend AI credits at all. Confirm that with action get before describing a specific Action as credit-free, rather than assuming it from the name.

Do not infer authorisation from an older message, a node label, an unfinished placeholder, or nearby canvas content. “Set up a product-shot workflow” is not approval to generate a product shot.

Compliance looks like. You describe what the run will cost the user in plain terms — which provider connection, roughly how long, what it produces — then wait for a yes in that turn. Then one run, one idempotency key.

Rule 3 — Never auto-retry a paid request

The rule. A terminal failure, a timeout, a dropped connection, or an ambiguous provider response ends the attempt. You report it. You do not silently start a second one.

The mechanism. The failure modes that most tempt an agent to retry are exactly the ones where the first request may still be in flight. A provider timeout is not proof that nothing was generated; it is proof that you stopped waiting. Retrying with a new idempotency key defeats the deduplication that would otherwise protect the user, and produces two charges for one intent.

The CLI encodes the same asymmetry at the transport layer: transient network errors and 429, 502, 503, and 504 responses are retried automatically — but only for safe GET requests, respecting Retry-After. Paid generation writes are never retried automatically. Mirror that rule in your own logic.

When a Run does fail, the terminal Run resource carries a stable failure.code and a failure.retryable flag. retryable: true is information for the user’s decision, not permission for yours.

Compliance looks like. Report the terminal state, the failure.code, and the handle. Ask whether the user wants another attempt. If they say yes, that is a new intent — use a new idempotency key.

Rule 4 — Credentials never enter chat, URLs, logs, screenshots, or repositories

The rule. A Gavana token (cba_…), an OAuth access token, and a webhook signing secret are all secrets. They belong in a private credential store and nowhere else.

The mechanism. Anything you put in a chat message enters a transcript that may be logged, replayed, and exported. Anything in a URL enters browser history, proxy logs, and referrer headers. Anything in a shell command enters shell history and, briefly, the process table where any local user can read it. Anything in a repository enters every clone of it, forever, including after a commit that appears to delete it.

Gavana gives you supported paths that avoid all of this:

  • The CLI reads a token from stdin (gavana auth login --token-stdin), never from an argument. See Install the CLI for the exact zsh, Bash, and PowerShell forms.
  • Browser OAuth (gavana auth login) means no token is typed anywhere. On macOS the resulting revocable token goes into Keychain under the service name ai.gavana.cli; other platforms and custom config paths use a mode-0600 file.
  • Webhook secrets are read from an environment variable you name with --webhook-secret-env, never from a flag value. The CLI never prints them.
  • MCP tool schemas deliberately omit webhook secrets, so a signing secret cannot enter model-visible tool arguments or tool logs. Webhooks are an API and CLI feature on purpose.

Compliance looks like. If a user pastes a token into a conversation with you, tell them to revoke it and issue a new one. It is burned. Do not use it, do not echo it back, and do not store it.

Rule 5 — Share only X-Request-ID with support

The rule. When something fails and a human needs to investigate, hand over the request ID. Nothing else from the request.

The mechanism. Every Gavana API response carries an X-Request-ID header, and the CLI surfaces it as requestId on the error object and, where the server did not already provide one, on the successful result too. It identifies the exact server-side trace of that one call. Support can look it up without any credential, any canvas content, or any provider configuration from you.

The alternative — pasting the whole failing command, the token, the request body, or a screenshot of the terminal — leaks far more than the problem requires, and usually less of what actually diagnoses it.

Compliance looks like. The requestId, the failing operation by name, and the error code. That is a complete report.

Rule 6 — Use only handles the service returned

The rule. Copy handles from responses. Never construct, decode, guess, or pattern-match one into existence.

The mechanism. Gavana handles are stable references with a deliberate shape:

canvas:CANVAS_ID canvas:OWNER_UID:CANVAS_ID node:NODE_ID connection:CONNECTION_ID asset:ASSET_ID asset:OWNER_UID:ASSET_ID shared asset recipe:RECIPE_ID action:SLUG model:OPAQUE_MODEL_KEY run:RUN_ID job:JOB_ID

Model keys in particular are opaque — the visible part of a model: handle is not a provider model name you can reassemble, and the same provider model may exist on more than one saved connection. gavana video generate refuses a bare provider model id that resolves ambiguously and tells you to use the model: handle from model list.

Owner-qualified forms matter too. Use the asset:OWNER_UID:ASSET_ID handle exactly as returned for a shared asset; it lets Gavana read that specific asset directly instead of searching another user’s library.

Invented handles do not silently do the wrong thing — they are rejected with a validation or not-found error. The cost is a wasted turn and a confused user, not corruption. But an invented handle inside a batch is worse: a batch that references a node which does not exist fails as a unit, and the recovery path is to re-read rather than to guess again.

Compliance looks like. Every identifier in your request can be traced back to a specific earlier response in the same session.

Rule 7 — Reuse a caller-stable idempotency key

The rule. One intended operation gets one idempotency key. Retrying the same intent reuses that key. A changed intent gets a new one.

The mechanism. Gavana deduplicates on the key. Retry the identical intended payload with the identical key and you get the original result rather than a second canvas, a second node, or a second charge. Keys are 8–200 characters. The CLI generates a UUID when you do not supply one, which is right for a one-shot command and wrong for anything you might need to resume — supply your own with --idempotency-key when a retry is plausible.

Two edges worth knowing:

  • With --destination new-canvas, the key is the identity of the destination. The CLI derives the new canvas id from a hash of the key, so re-running the same command with the same key targets the same canvas instead of creating another one. That is why new-canvas requires an 8–200 character key.
  • Reusing a key with a different payload can return conflict (409) instead of the result you expected. For automatically created image targets the CLI says so directly: use a new key, or pass explicit --target nodes.

Compliance looks like. The key is derived from the user’s intent — a task id, a hash of the brief — not from the current timestamp. Same intent, same key. New intent, new key.

Rule 8 — Read baseRevision before a raw canvas write

The rule. Any operation that changes an existing canvas carries the revision you read, as baseRevision.

The mechanism. This is optimistic concurrency. The server compares your baseRevision against the canvas’s current revision. If a human moved a node in the browser between your read and your write, the revisions no longer match and the write is rejected with 409 — instead of overwriting their change.

The correct recovery is not to re-send the same operations with a fresher revision. It is to read again, look at what changed, rebase your intended addition around the newer graph, and preserve the concurrent user change. Reuse the same idempotency key only if the intended payload is genuinely unchanged; if you rebased, that is a new payload and a new key.

The CLI does the read for you when you omit --base-revision on node, connection, and canvas apply commands. That convenience is safe for a single quick command and is not a substitute for reading when you are reasoning about the canvas — a read you never looked at teaches you nothing.

Compliance looks like. Read, plan against what you read, write with that revision, handle 409 by reading again rather than by forcing.

The preservation contract

Rules 1 and 8 have a consequence worth stating separately, because it is the one agents most often get wrong on canvases that already contain work:

When the user says “add”, that does not mean “reorganise”.

Make the smallest delta that satisfies the request. Do not move, resize, retitle, reconnect, delete, or rewrite an existing node outside the requested scope. Compute the current visible bounding box and place new work outside it — to the right with at least 160 canvas units of outer spacing, or below with the same spacing if right-side placement would make the canvas unreadably wide. Use 48 units of inner Section padding, 32 units between sibling nodes, and at least 80 units between major stages. An agent-authored addition should be removable without damaging surrounding user work.

Validation findings are review items, not permission. canvas_validate reporting an overlap in a region you did not touch is not an invitation to tidy the user’s canvas.

What a compliant turn looks like

Identify one canvas

One exact handle. If the user’s description matches two canvases, ask which one — do not pick.

Read it

canvas_get, or gavana canvas get. Retain the revision and the node handles.

Consult the guide if the operation is unfamiliar

guide_search then guide_get, or the MCP resources under gavana://guides/canvas/v1/. The guide topic IDs are getting-started, notes-text-sections, sections-layout, connections, prompt-lists, generated-assets, existing-canvases, paid-action-safety, validation-recovery, and examples-common-mistakes.

Plan the smallest change

Preserve unrelated nodes, connections, positions, and metadata.

Validate the proposal

For spatial, multi-node, or destructive work, call canvas_validate with the proposed operations. Read the destructive impact before applying it.

Apply one atomic batch

With the revision you read and one caller-stable idempotency key. Related nodes and connections go in the same batch, so a malformed operation leaves no partial graph.

Validate again and report

Report the exact changed handles and any warning you deliberately left unresolved. Handles are the audit trail.

For paid work, insert an explicit approval step between planning and execution, and never let a failure loop straight back to execution without a new human decision.

Reporting durability honestly

A last rule, really a corollary of rule 6: do not claim durability you have not observed.

A generated image is durable when the result returns a target node: handle, a durable asset: handle, and the final canvas read shows server-owned media fields. A video Job may return a protected download without materialising a native video node — report exactly what the server returned rather than what usually happens.

The observation handles themselves expire. Image and Action run: handles and their legacy job: aliases address the same temporary record: the default retention is 24 hours while unacknowledged, 15 minutes after the first successful server finalisation, and seven days for the expired tombstone. Recipe Run records currently persist without that temporary TTL. The asset: and node: handles are what remain durable — those are what you hand back to the user.

Last updated on