Skills

Drafts, validation, publishing, runs, automation, integration bindings.

Raw Markdown for agents: skills.md. MCP: read_guide("skills").

Skills are AI processes owned by the tenant. A Skill has an immutable active version (what runs) and an optional draft (the working copy). skills.get returns both. skills.rename changes the name at once, without a draft; everything else (summary, instructions, inputs, outputs, scope, integrations, scripts, settings) goes through the draft.

Flow

  1. skills.create opens a draft, empty, with content, or from a template found with skills.search. Each user holds one unsaved draft per kind; a call with different content fails with skill_draft_exists.
  2. skills.draft.save replaces the whole working copy. Start from skills.get and send every field that should remain, changed or not; send the current draft_revision as expected_revision. The response carries issues, the same list skills.validate returns.
  3. skills.validate checks the draft (or the published version when no draft exists) exactly like a publish and returns valid plus every issue with path, code, message, hint and blocking. Fix every blocking issue with another skills.draft.save, then validate again.
  4. skills.integration_bindings.list with skill_id and space_id shows the integration requirements and which of them the caller has bound; before the first publish it reads them from the draft (source: "draft").
  5. skills.save publishes: it validates again, stores a new immutable version, makes it active and clears the draft. MCP clients may call it; in the in-app assistant it asks the user for approval. Publish only when the user asked for it, never as a follow-up to a rename or a draft save; otherwise report that the draft is still unpublished.
  6. skills.integration_bindings.set binds each requirement to one of the caller's own active Space grants. It needs a published version.
  7. skills.automation.set stores the triggers and the run request of the Skill in a Space. Active triggers need a published version.
  8. Webhook test: while the webhook is inactive, skills.automation.webhook.listen captures the next authenticated delivery within 10 minutes instead of running it; skills.automation.get then shows it under webhook.sample.payload, the item a script for this trigger receives (body is the provider object).

When changing a published Skill the version already exists, so bindings and automation can be set before the next skills.save.

Other operations: skills.draft.discard drops the working copy of a Skill that has an active version (a never-published Skill is deleted with skills.delete instead). skills.restore creates a new active version from an earlier one and drops the draft; it asks the user for approval. skills.versions lists versions. skills.delete is irreversible and fails while runs exist.

Running: skills.runs.start starts one run in a Space with inputs and documents; runs cost credits. skills.runs.get and skills.runs.events (cursor) show status, progress and results; skills.runs.cancel stops a run. skills.models lists the AI models a Skill may use.

Rules

  • instructions and capabilities are required for a publish. capabilities is explicit: list every capability the Skill uses. Known capabilities: browser, email, folders, inbox, integrations, items-output, pages, persistent-output, records, reviewer, sheet-output, web-search.
  • Records: a Skill that reads or writes records lists records in capabilities and the record type ids in scope.recordTypeIds (records.types.list returns them). Scripts reach only record types in that scope.
  • steps no longer exists and is rejected; write the order as numbered lines in instructions.
  • The definition model is strict: unknown fields fail on every level. Inputs need key (lowercase letters, digits, _), type (text or document) and label; outputs need key, type (doc, sheet, page or items) and nameTemplate; keys are unique. Integrations need key, providerKey and operations of {resource, operation}; scripts need scriptId, an explicit version and an alias. entry: {"kind": "script", "alias"} needs that alias under scripts and exactly one output of type items.
  • Field bindings under integrations[].operations[].fields are fixed (value) or choice (values); they are checked against the provider's operation contract.
  • Integration requirements are logical: the definition never stores grants or connections. Each user binds them personally per Space; unbound requirements block runs and script entries.
  • Folder and document ids come from documents.folders_list and documents.list; never invent ids.
  • skills.automation.set: a run request (run with scope, inputs or an instruction) needs at least one trigger after the call. Omit webhook to keep the current one; null removes it. run.scope.record_type_ids needs records in the Skill's capabilities.
  • A publish checks: name and summary present, the definition model, field bindings, the pinned scripts (they belong to the Skill, the version exists, the static check passed, slots and operations match the Skill's integrations, record types lie in the scope, every @script:<alias> in the instructions is pinned) and the settings the platform offers (agentScriptsEnabled, subagentsEnabled, securityReviewEnabled needs the plan feature). An outdated pinned version, a missing dry run and record types outside a declared record scope are hints and do not block.
  • Importing a Skill from another tool: map what the draft supports (instructions, inputs, outputs, scope, integration requirements, settings); ids, bindings and fields of the other system do not transfer. List in the answer what was left out.

Automation

Up to three triggers per Skill and Space: a schedule (kind rule, cron or once; workflows.schedule.preview shows the next run times), a platform event and an inbound webhook. Each trigger has an entry: {"kind": "agent"} starts the Skill agent with the stored run request; {"kind": "script", "alias", "parameters", "escalate"} runs one pinned script without a model call and, with escalate true, starts the agent only when the script fails or returns exceptions. A script entry needs the caller's own integration bindings. A webhook (auth none, secret_header, hmac_signature, or integration with a provider such as Shopify in mode subscription over a grant, or manual) receives as soon as it is configured and executes only while active. The webhook secret is never shown in chat; the user reads it in the Skill's automation panel. The result of skills.automation.set is the effective automation, the same shape as skills.automation.get.

Errors and fixes

  • validation_error from skills.save: details maps each definition path (for example outputs.0.nameTemplate, capabilities, scripts.1) to its message. Call skills.validate for the full list with hints, fix the draft, validate again.
  • script_target_out_of_scope: a pinned script reads or writes record types the Skill cannot reach. Blocking when capabilities lacks records and scope.recordTypeIds is empty: add both. A hint when the record types are only missing from scope.recordTypeIds: add their ids.
  • skill_draft_exists (409) from skills.create: you already hold an unsaved draft. Read it with skills.get(details.skill_id) and continue it with skills.draft.save, or delete it with skills.delete.
  • draft_revision_conflict (409): the draft changed elsewhere. Read it again with skills.get and retry with the current draft_revision.
  • automation_run_scope_requires_trigger: run carries a scope, inputs or an instruction, but schedule, event and webhook are all missing. Send a trigger in the same call, or send run with empty inputs and scope to remove the automation.
  • skill_not_saved (409): the operation needs a published version. Call skills.save first.
  • webhook_mode_invalid (409): the webhook's auth mode does not fit the call, for example a signing secret for a webhook that is not a manually registered provider webhook, or a secret for auth none.
  • integration_grant_missing (in details.webhook): a subscription webhook names no active grant of this Space. Create or pick one with integrations.grants.list or integrations.grants.create and pass its id as webhook.integration.grant_id.
  • integration_binding_required: a script entry or run needs the caller's bindings. Bind them with skills.integration_bindings.set.

Example

A Skill that receives Shopify orders by webhook and writes them to records through a script. The script orders was created with scripts.create, tested with a dry run and is pinned in version 3.

{
  "name": "Order sync",
  "summary": "Writes every new Shopify order into the Orders records.",
  "instructions": "1. Run @script:orders with the delivered orders.\n2. When the script reports exceptions, check each order and explain what is missing.",
  "capabilities": ["integrations", "items-output", "records"],
  "scope": {"recordTypeIds": ["<orders record type id>"]},
  "integrations": [
    {
      "key": "shop",
      "providerKey": "shopify",
      "operations": [{"resource": "orders", "operation": "get"}]
    }
  ],
  "outputs": [
    {"key": "orders", "type": "items", "nameTemplate": "Synced orders"}
  ],
  "scripts": [
    {"scriptId": "<script id>", "version": 3, "alias": "orders"}
  ],
  "entry": {"kind": "script", "alias": "orders", "escalate": true}
}

Send it as the draft of skills.draft.save, check it with skills.validate, publish with skills.save (the same fields, with name, summary, categories and tags as separate arguments and the rest as definition), bind shop with skills.integration_bindings.set, then:

{
  "space_id": "<space id>",
  "skill_id": "<skill id>",
  "schedule": null,
  "event": null,
  "webhook": {
    "auth": "integration",
    "integration": {
      "provider_key": "shopify",
      "mode": "subscription",
      "grant_id": "<grant id>",
      "events": ["orders/create"]
    },
    "active": false,
    "entry": {"kind": "script", "alias": "orders", "escalate": true}
  },
  "run": {"inputs": {}, "scope": {"record_type_ids": ["<orders record type id>"]}}
}

Capture a test delivery with skills.automation.webhook.listen, check webhook.sample.payload in skills.automation.get, and let the user switch the webhook to active.

Auf dieser Seite