> ## Documentation Index
> Fetch the complete documentation index at: https://www.plungeai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> Your workflows are saved CNL workflows. CNL is the platform's YAML workflow language. Users call their workflows "my agents", and most day-to-day work is here: find a workflow, run it, inspect it. There is no separate "MCP workspace". A workflow you create or run here is the same workflow you …


Source: `orchestration/mcp-gateway/tools.ts` (`ExecuteWorkflowSchema`, `executeWorkflow`, `ListWorkflowsSchema`, `listWorkflows`, `WorkflowSchema`, `plungeaiWorkflow`, `BuildWorkflowSchema`), `orchestration/mcp-gateway/workflow-store.ts`, `orchestration/mcp-gateway/chat.ts` (`buildWorkflow`), `orchestration/mcp-gateway/extras.ts` (`plungeaiTemplates`), `orchestration/mcp-gateway/execution.ts` (`runExecution`, `executeViaStream`), `orchestration/mcp-gateway/execution-do.ts`, `orchestration/mcp-gateway/leg-state.ts`, `orchestration/mcp-gateway/tool-outcome.ts`, `orchestration/mcp-gateway/server.ts`, `orchestration/cnl-engine/cnl-engine.ts` (`loadWorkflow`, request `input` / `inputs`), `agents/agents/one-agent/call-config.ts`, `agents/agents/one-agent/call-mode.ts`, `core/core-base/provider-ids.ts`, `apps/ocean-skills/skills/plungeai-workflows/references/cnl-spec.md`.

**Your workflows** are saved CNL workflows. CNL is the platform's YAML workflow language. Users call their workflows "my agents", and most day-to-day work is here: find a workflow, run it, inspect it. There is no separate "MCP workspace". A workflow you create or run here is the same workflow you see in Studio and the apps, and every change syncs live.

| Job | Tool |
|---|---|
| Find your workflows and folders | `plungeai_list_workflows` ([8.1](#81-find-your-workflows--plungeai_list_workflows)) |
| Run a saved workflow, or ad-hoc YAML | `plungeai_execute_workflow` ([8.2](#82-run-a-workflow--plungeai_execute_workflow)) |
| Create, read, edit, delete, version | `plungeai_workflow` ([8.6](#86-manage-saved-workflows--plungeai_workflow)) |
| Generate or refine one from plain language | `plungeai_build_workflow` ([8.8](#88-generate-a-workflow--plungeai_build_workflow)) |
| Copy a ready-made template into your account | `plungeai_templates` `use` ([8.9](#89-start-from-a-template--plungeai_templates-use)) |

Base URL `https://mcp.plungeai.com/v1`. `https://api.plungeai.com/v1/mcp` is the same server; the REST twin is https://api.plungeai.com/docs#10-workflows-plane. Always pass `user_request`: for "show me all my agents in the finance folder", the arguments alone are just `{folder: "Finance"}` and lose the intent. Parameter types and limits are in the [Tool reference](/mcp/tool-reference).

## 8.1 Find your workflows — `plungeai_list_workflows`

| You send | What comes back |
|---|---|
| nothing | `# Your Workflows (N most recent)`, then `**Folders:** Finance (15) · Personal (23) · …` (only folders that hold something, in your sidebar order), then a table of your 50 most recently updated workflows |
| `folder` | Workflows whose folder name **contains** this text (case-insensitive) |
| `search` | Workflows whose **name** contains this text (case-insensitive). Descriptions are not searched |
| `kind` | Only that kind: `workflow`, `agent`, `bot` or `campaign` (exact) |
| `folder` + `search` + `kind` | All given must match |
| `limit` | 1 to 100 (default 50) |
| `format: "json"` | `{"workflows":[{"id","name","description","kind","folder"}]}` |

The table columns are Name, Kind (`workflow`, `agent` or `bot`), Folder, ID, Description, newest first. The ID is what `plungeai_execute_workflow` and `plungeai_workflow` take. With no match you get "No workflows matched. List without filters to see your folders." An empty account answers "No workflows found. Create one with plungeai_workflow or plungeai_build_workflow." The tool is read-only.

### Try asking

- *"Show me all the workflows in my Finance folder."*
- *"What folders do I have and how many workflows in each?"* For example: Finance 15 · Personal 23 · Technology 11 · Business Strategy 14 · Blockchain 8.
- *"Find my workflow about due diligence."* This searches your workflow **names**, so a workflow called "DD Memo" is not found by "due diligence". List the folder instead if the name is different.

```bash
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_list_workflows","arguments":{"user_request":"What folders do I have and how many workflows in each?"}}}'
```

> **"Category" and "folder" are different things.** Your *folders* (Finance, Personal, …) hold **your workflows**. *Categories* are the registry's agent taxonomy ([Discovery](/mcp/discovery)), where the finance one is called `financials`.

## 8.2 Run a workflow — `plungeai_execute_workflow`

| Argument | Meaning |
|---|---|
| `workflow_id` | A saved workflow you own (the ID from 8.1) |
| `workflow_yaml` | Ad-hoc CNL, up to 256 KiB ([8.3](#83-ad-hoc-cnl-yaml)) |
| `input` | The primary input, available as `{input}` |
| `inputs` | Named inputs `{key: value}`, available as `{key}` |
| `mode` | `sync` (default) or `async` ([8.4](#84-sync-and-async)) |
| `format` | `markdown` (default) or `json` |

Send `workflow_id` **or** `workflow_yaml`. With neither you get `needs_input` ("Provide either workflow_yaml (ad-hoc CNL) or workflow_id (a saved workflow)."). If you send both, `workflow_id` is used and the YAML is **ignored**.

**Saved workflows.** The id must be a workflow on *your* account. An unknown id, or one owned by someone else, returns `needs_input` ("No saved workflow with id `<id>` on this account.") with a pointer to `plungeai_list_workflows`, and no run. The run is named after the saved workflow. Its title in Studio's sidebar is your `input` (or the `inputs` values, or your `user_request`), the same as a run started in Studio.

### Try asking

- *"Run the Acquisition Scanner with Adobe as the input."* This runs a saved workflow. Most finish in seconds, because the engine fans out over agents in parallel.
- *"Show me my workflows in the Finance folder, then run the Acquisition Scanner with Tesla as the input."* One request, two steps. Your client lists the folder to find the workflow you meant, then runs it with your input.
- *"Run my Company Snapshot Research Brief with Apple as the input."* The same pattern: swap in whichever workflow and company you mean.

This runs your workflow's agents (it costs credits and writes an execution; not run by the docs verifier):

```bash
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_execute_workflow","arguments":{"user_request":"Run the Acquisition Scanner with Adobe as the input.","workflow_id":"<workflow id from plungeai_list_workflows>","input":"Adobe"}}}'
```

A saved workflow's agents are **not** re-checked by the gateway at run time. Only ad-hoc YAML goes through the active-only fence (8.3). If a saved workflow names an agent that is no longer deployed, the run fails in the engine and comes back `unavailable` ("registered but not deployed").

## 8.3 Ad-hoc CNL YAML

`workflow_yaml` runs CNL without saving it. The `workflow:` wrapper is optional: bare top-level `name` + `tasks` is accepted and wrapped for you (Form B in the CNL spec). This example needs `jq` to build the JSON. It runs three agents (it costs credits; not run by the docs verifier):

```bash
YAML=$(cat <<'CNL'
name: Quick brief
tasks:
  - type: parallel
    id: research
    subtasks:
      - id: web
        agent: brave-agent
        prompt: "{input} — latest news"
      - id: deep
        agent: tavily-agent
        prompt: "{input} — cited sources from the last 12 months"
  - id: brief
    agent: one-agent
    prompt: |
      Write a five-bullet brief on {input}.
      Brave: {data:web}
      Tavily: {data:deep}
CNL
)
jq -n --arg yaml "$YAML" '{jsonrpc:"2.0",id:1,method:"tools/call",params:{name:"plungeai_execute_workflow",arguments:{user_request:"Quick brief on Cloudflare Workers",workflow_yaml:$yaml,input:"Cloudflare Workers"}}}' |
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d @-
```

What the gateway does before the engine sees it (`executeWorkflow`):

1. **Parses** the YAML. If it cannot be parsed, you get `needs_input` ("Your workflow_yaml could not be parsed. \<parser message\>"), and the message points only at *your* text. Valid YAML that is a bare scalar (a string, a number) or empty returns `needs_input` ("workflow_yaml must be a CNL mapping with a `workflow:` (or top-level `name`/`tasks`), not a bare value."). Nothing runs in either case. A top-level **list** is not caught here: it is wrapped and dispatched without the agent fence below, fails in the engine, and leaves a failed execution in your history. Always send a mapping with `workflow:` or top-level `name`/`tasks`.
2. **Fences the agents.** The `agent:` ids of your tasks are checked against the registry, including inside `subtasks`, batch `tasks` and `executors`, plus a harness task's `mission_ref` (`collectAgentIds`). Unknown or not-`status:active` ids return `unavailable`, naming each offender ("No such agent: …", "Not permitted: … not `status:active`"). Nothing runs. Agents named elsewhere (a `dynamic` generator, debaters, judges, validators) are resolved by the engine when the run starts.
3. **Wraps and runs.** Your `input` and `inputs` travel with the workflow and fill `{input}` and `{key}`. The run is named `<YAML name> · <first three agents>` (`+N` for more). After the run, the name is replaced by the agents that actually ran. Every run also carries `user_context` (your time zone and approximate place, taken from the request's network location), which the agent uses for local time and as the default time zone for a schedule.

The gateway does not run the CNL schema validator on ad-hoc YAML, and the engine records schema findings without blocking the run (its validation mode is record-only; `cnl-engine.ts`). A malformed document is dispatched and fails however its tasks fail, and that leaves a failed execution in your history. To check a document first, save it with `plungeai_workflow` `create`: that call runs the validator (`yamlToCNL`) and rejects errors such as `Workflow name is required and must be a string` or `Type 'harness' requires a 'goal' string` before anything is stored. Or let `plungeai_build_workflow` write the YAML.

### Writing YAML by hand

- **Quote your colons.** `prompt: DD: memo` breaks YAML. Write `prompt: "DD: memo"`, or put long text in a block scalar (`prompt: |`). Never hard-wrap a value across lines. An `invalid yaml` failure comes back with exactly this tip.
- **Structured tool-agents take their card's fields as task fields**, next to `agent:`:

  ```yaml
  - id: wx
    agent: weather-agent
    operation: forecast
    city: "Sun Valley,ID,US"
    units: imperial
  ```

  Watch for card fields that share a name with a CNL task field. `location` is one: on a task it means `cloud` or `local` (where the task runs). A weather task with `location: "Sun Valley,ID,US"` runs as ad-hoc YAML, because the field passes through to the agent, but the validator used by `plungeai_workflow` and `plungeai_build_workflow` rejects it (`Invalid location: Sun Valley,ID,US. Must be 'cloud' or 'local'`). In a saved workflow, use the weather card's alias `city` (as above) or `lat`/`lon`.

- **On a `one-agent` task, prefer `provider: gemini` or omit `provider`.** Omitting it gives the default (`gemini`, model `gemini-3.1-flash-lite`, `agents/agents/one-agent/call-config.ts`). `google` is accepted and mapped to `gemini` by the provider resolver (`core/core-base/provider-ids.ts`), but one-agent then does not apply its default model (it does so only for an omitted or literal `gemini` provider), so pass `model` explicitly if you write `google`. Any other unknown provider is refused (`Unsupported provider: <x>`, [Troubleshooting](/mcp/troubleshooting)).
- **Take agent ids from a live search**, never from memory: it is `brave-agent`, not `brave-search` ([Discovery](/mcp/discovery)).
- **Outward operations are fenced here too.** A task whose `operation` or `action` is `send`, `pay`, `buy`, `transfer`, … is refused by the engine as `needs_approval` ([6.6](/mcp/tools#66-the-trust-fence--needs-approval)). Nothing runs, and `plungeai_continue` cannot resume it: such a task runs only from Ocean Studio.
- **There is no `schedule:` field.** Schedule a workflow with `plungeai_schedule` ([Scheduling](/mcp/scheduling)).

Easier still: describe the goal and let *"build me a workflow that…"* write the YAML for you ([8.8](#88-generate-a-workflow--plungeai_build_workflow)).

## 8.4 Sync and async

**Sync** (the default) runs the workflow and streams live status while it works. The lines come from the engine's own task events, the same ones Studio's live view renders (`executeViaStream`):

```
⚙️ Agents working — 5/18 agents complete · ✓ brave-agent · 13 active (tavily-agent, exa-agent, serper-agent…)
✍️ Generating answer — 16/18 agents complete · 2 active (one-agent)
✍️ Writing (3516 chars) — …the answer as it is produced
⚙️ one-agent still working — 45s elapsed…
✅ Complete — 18 agents in 24.6s
```

A keepalive (`⏳ Working — Ns elapsed`) is sent every 15 seconds so the stream never goes idle. The feed goes out on `notifications/progress` when your client sent a progress token, otherwise on `notifications/message`. **Whether it is displayed is up to your client**; the work and the result are the same either way ([4.6](/mcp/protocol#46-toolscall-over-sse-progress-and-keepalive)). A quiet client is not a stalled run: the run is visible in Studio's sidebar the whole time.

**Async** (`mode: "async"`) returns at once with the execution id. The tool description suggests it for runs longer than about 3 minutes, and it is the way around Claude Desktop's roughly 4-minute cap on a single call:

```markdown
Started execution `<execution id>` (async). Poll plungeai_get_workflow_status with this id, then fetch output with plungeai_get_result once completed. If the status reports ⏸ AWAITING USER APPROVAL or AWAITING USER, relay it to the user and continue with plungeai_continue.

---
execution: `<execution id>` — use this id for plungeai_get_result / plungeai_followup on this run.
```

An async run has no live feed. It is held by its own Durable Object in resumable ten-minute legs, so it survives your client disconnecting ([7.9](/mcp/agents-missions#79-async-missions-the-ten-minute-relay)). Poll `plungeai_get_workflow_status`, then fetch `plungeai_get_result` ([Runs, results & follow-ups](/mcp/runs-results)). A running execution that stops heartbeating for 16 minutes is marked failed, with `Interrupted (run did not complete)`. Rerun it, in the foreground if it is short.

## 8.5 What a run returns

| Outcome | When |
|---|---|
| `ok` | The final task's output, then the execution footer (`summary`: "Workflow completed."). If the final task stored nothing, the stored outputs of the individual tasks are joined under `## <task id>` headings instead. An `async` start is also `ok` |
| `needs_approval` / `needs_input` | A task paused for approval or a question (`⏸ AWAITING USER APPROVAL: …` / `⏸ AWAITING USER: …`). Continue with `plungeai_continue` after the user decides |
| `needs_approval` (no actions) | A task's `operation`/`action` is on the engine floor list. Nothing ran, and it cannot be continued over MCP ([6.6](/mcp/tools#66-the-trust-fence--needs-approval)) |
| `needs_input` | No workflow given, unknown saved id, unparseable YAML, or an agent rejected its inputs |
| `unavailable` | Ad-hoc YAML names an unknown or inactive agent, or an agent is not deployed |
| `needs_connection` / `needs_api_key` | A task hit a missing connected account or key at run time. Workflows have no pre-flight, so the answer names "the required service" ([6.7](/mcp/tools#67-connected-accounts-and-your-own-keys)) |
| `error` | A genuine failure with the real reason. A timeout suggests `mode: "async"` |

Every outcome follows the vocabulary in [6.4](/mcp/tools#64-the-outcome-vocabulary).

Two failure shapes are specific to runs. If the engine reports success but nothing was stored, the run is failed: "Engine reported success but no result was stored (final_task: …). The agent likely failed in-band without storing output — check its own logs." If the final answer *starts* with a refusal marker (`# ❌`, `Error: `), the run is failed with that text, never shown as a success.

A run whose final harness task ran out of turns is stored as `incomplete` and reported that way by `plungeai_get_workflow_status`, but only for **async** runs. A sync run's answer is `ok`, and the sync path stamps the outcome `ok` afterwards, replacing the `incomplete` mark ([7.7](/mcp/agents-missions#77-mission-bounds)).

**Every run is visible everywhere.** It appears in Studio's results sidebar and the ops dashboard. The title is your question, and the subtitle is the workflow plus the agents that actually ran, the same as a run started in Studio. The result is final Markdown (tables, headings, links). Ask for it "in full" if your client tends to summarize. `plungeai_get_result` adds a **"Steps in this run"** index for multi-task runs, so any step's full output is one ask away ([Runs, results & follow-ups](/mcp/runs-results)).

## 8.6 Manage saved workflows — `plungeai_workflow`

Direct edits to your saved workflows. The store is byte-compatible with Studio's (D1 `yaml_workflows` + KV `workflow:{user}:{id}`), and every write is ownership-checked. Create, update and delete are broadcast and sync live to Studio, iMini and the other apps. `save_version` and `restore_version` are not broadcast, so reload an open Studio editor after a restore (saving from a stale editor would overwrite the restored YAML).

| `action` | Needs | Optional | Does | Answers |
|---|---|---|---|---|
| `create` | `name`, `yaml` | `description`, `folder`, `kind` | Validates the CNL, saves, files into the folder | "Created workflow **\<name\>** (`<id>`). Filed under **\<folder\>**. Synced to all apps." plus any validation warnings |
| `get` | `workflow_id` | — | Reads the saved definition | `# <name>`, the description, and the YAML in a code block |
| `update` | `workflow_id` | `name`, `description`, `yaml`, `folder`, `kind` | Changes only what you send. New YAML is validated first. Moving `kind` away from `bot` or `campaign` runs the same clean-up as `delete` for the old kind (below) | "Updated workflow `<id>`. Synced to all apps." plus " Moved to folder **\<folder\>**." / " Kind set to **\<kind\>**." |
| `delete` | `workflow_id` | — | Deletes the workflow (the D1 row and its live content). A bot also loses its memory namespace `<you>:bot:<id>` and its scheduler jobs; a campaign loses its scheduler jobs and its ledger is tombstoned | "Deleted workflow `<id>`. Synced to all apps." |
| `save_version` | `workflow_id` | `version_description` | Snapshots the current YAML as the next version ([8.7](#87-versions)) | "Saved version V\<n\> (`<version id>`)." |
| `list_versions` | `workflow_id` | — | Lists the snapshots, newest first | A Version / ID / When / Note table, or "No versions yet. Use action "save_version"." |
| `get_version` | `workflow_id`, `version_id` | — | Reads one snapshot | `# V<n>` and its YAML |
| `restore_version` | `workflow_id`, `version_id` | — | Makes that snapshot live again | "Restored — new version V\<n\>." |
| `permissions_get` | `workflow_id` (a saved bot) | — | Reads its permission classes, locks and spend caps, and `effective`: the preset each of the four classes (`send`, `write`, `pay`, `delete`) runs under, the stored one or else `allow` | classes, effective, locks, `budget_usd_run`, `budget_usd_month`, and the same as JSON |
| `permissions_set` | `workflow_id` | `classes`, `locks`, `budget_usd_run`, `budget_usd_month` | Each field present replaces the stored one; `locks` only for a team admin over the workflow (otherwise ``Error: … (lock_admin_required)`` and nothing is written) | "Updated." and the new policy |
| `files` | `workflow_id` | — | Lists the bot's persistent workspace | a Path / Size / Type / Updated table |
| `file` | `workflow_id`, `path` | — | Reads one workspace file, at most 5 MB | the bytes as a base64 block (larger: use the One API download route) |
| `inbox` | `workflow_id` | — | Reads the bot-to-bot messages queued for its next run | a From / When / Message table |
| `bindings` | `workflow_id` | — | Lists the paired chats bound to the bot | a Channel / Sender id table, or a note that bindings are not enabled yet |
| `bind`, `unbind` | `workflow_id`, `channel`, `sender_id` | — | Binds a chat you already paired to the bot, or releases it | a confirmation, or ``Error: … (not_paired)`` |
| `groups` | — | — | Lists your bot groups | a Name / Members / ID table |
| `group_set` | `group`, `members` (2–6 bot ids) | — | Creates the group, or replaces the members of the one with that name | "Created" / "Updated group …" |
| `group_delete`, `group_thread` | `group` | — | Deletes the group, or reads its shared thread | a confirmation, or the thread table |

**Validation.** On `create`, and on `update` with `yaml`, the YAML goes through the engine's CNL validator (`yamlToCNL`) before anything is stored. An error rejects the call: "Workflow YAML failed CNL validation: \<field\>: \<message\>, …" (`isError`), and nothing is saved. Warnings (near-miss keys such as `input` on a task where `prompt` was meant) are appended as "⚠️ Validation warnings:" and do not block. The same check runs the save-time kind lint: `memory_owner` in the YAML is an error for every kind (memory tenancy comes from the executing user), and an `agent` or `bot` must be exactly one `type: harness` task whose `allowed_tools` (if set) include `task_complete`. The kind linted is the `kind` you pass, else the stored one on `update`. Hardcoded emails or UUIDs only warn. The validator checks the schema, not the agents. A saved workflow can name an inactive agent and fail only when it runs, so take ids from [Discovery](/mcp/discovery).

**Folders.** `folder` is matched against your folder names: an exact case-insensitive match, otherwise a *unique* partial match. A name that matches nothing **creates that folder** (it appears in Studio at once) and files the workflow into it, on `create`, `update` and `templates use` alike. A name that partially matches two or more folders is an error naming them ("…is ambiguous — it matched 2 folders: Sales East, Sales West. Use the full name."), and nothing is changed. `folder: "none"` (or `""`) on `update` moves the workflow out of its folder.

**Kind.** `kind` picks the sidebar section: `workflow` (Flows, the default), `agent` (Agents), `bot` (Bot agents) or `campaign` (a list worked through on a cycle). A `campaign` create takes `campaign_config` (`{list, cycle, max_attempts, schedule?, deliver?, …}`): the config is checked before anything is saved, then the ledger and the scheduler job are set up and the campaign id is written into the YAML. Without `campaign_config` the campaign is saved with a warning and no ledger.

**Bot control.** The bot actions work on a saved bot you own and use the same stores as Studio's bot settings and the One API's `/v1/workflows/{id}/permissions`, `…/workspace-files`, `…/inbox`, `…/bindings` and `/v1/bot-groups`; a refusal reads ``Error: <message> (<code>)`` with the One API's code. Bindings need the message gateway and groups need their table: until then they answer a plain "not enabled yet".

**Errors.** A missing required field answers "Error: \<field\> is required for action "\<action\>"" (`isError`). An id that does not exist or is not yours answers "Workflow not found." or "Version not found." (`isError`), except `get`, which returns a `needs_input` outcome ("Workflow `<id>` not found.") pointing at `plungeai_list_workflows`. The bot actions (`permissions_get`, `files`, `inbox`, `bindings`, …) answer ``Error: no saved workflow with that id for this account (workflow_not_found)`` instead.

**Two things to know.** `update` does **not** save a version first (the same as Studio), so run `save_version` before an edit you may want to undo. `delete` is a hard delete of the workflow. Your past executions of it stay in your history. The clean-up that follows is best effort and never fails the call: for a bot it wipes only the memory namespace `<you>:bot:<id>` (older bots; a bot saved without `memory_owner` uses the memory of whoever runs it, which a delete never touches) and deletes the bot's scheduler jobs; for a campaign it deletes the jobs and tombstones the ledger. Changing a saved row's `kind` away from `bot` or `campaign` with `update` runs the same clean-up for the old kind.

### Try asking

- *"Show me my Acquisition Scanner workflow — what does it actually do?"* The client finds its id with `plungeai_list_workflows` and fetches the definition with `get`, so you can see exactly what it runs (which agents, in what order) before you trust it with real input.
- *"Rename my Acquisition Scanner, move it to the Finance folder, and save a version first."* The client runs `save_version`, then `update` with `name` and `folder`.
- *"Create a workflow from this YAML and put it in Finance."* `create` files it straight into the folder; no separate move is needed.
- *"What is my Outreach bot allowed to do without asking?"* The client runs `permissions_get`; *"Make it ask before sending anything"* is `permissions_set` with `classes: {"send": "ask"}`.
- *"Group my Outreach and Research bots as Sales."* `group_set` with `group: "Sales"` and the two bot ids; `group_thread` reads what they said to each other.

Reading a definition (read-only; it needs a real id, so it has no verifier marker):

```bash
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_workflow","arguments":{"user_request":"Show me my Acquisition Scanner workflow","action":"get","workflow_id":"<workflow id>"}}}'
```

Creating one into a folder changes your account (not run by the docs verifier):

```bash
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_workflow","arguments":{"user_request":"Save this as News Scan in my Finance folder","action":"create","name":"News Scan","folder":"Finance","description":"Latest news on a company","yaml":"name: News Scan\ntasks:\n  - id: news\n    agent: brave-agent\n    prompt: \"{input} latest news\"\n"}}}'
```

Reading a bot's policy is read-only (it needs a real id, so it has no verifier marker); `permissions_set` takes the same arguments plus the fields to change and writes them, and `bindings`, `files`, `inbox` and `groups` read the bot's paired chats, workspace, queued messages and groups the same way:

```bash
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_workflow","arguments":{"user_request":"What is my Outreach bot allowed to do","action":"permissions_get","workflow_id":"<bot id>"}}}'
```

Creating a bot group changes your account (not run by the docs verifier):

```bash
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_workflow","arguments":{"user_request":"Group my Outreach and Research bots as Sales","action":"group_set","group":"Sales","members":["<bot id>","<bot id>"]}}}'
```

The same workflow is also readable as the resource `plungeai://workflows/{id}` ([Prompts & resources](/mcp/prompts-resources)).

### A2A doors

Every bot, agentic agent and workflow can get an **Agent2Agent (A2A 1.0) door**: a URL under `https://a2a.plungeai.com/<handle>` with its own bearer token, through which any A2A client — another vendor's agent, the official `a2a-sdk` clients, or one of your own bots — sends it work. `plungeai_workflow` `action: "a2a_door"` manages it: `door_action: "mint"` creates the door and shows the token once (minting again rotates it), `get` shows the door, `pause` / `resume` close and reopen it, `revoke` deletes it. The door's agent card is at `<door url>/.well-known/agent-card.json`; a `SendMessage` runs the workflow as you and returns the result as a text artifact (bots and agentic agents continue the conversation when the caller reuses the `contextId`; a session that stops with a question answers `TASK_STATE_INPUT_REQUIRED`). Long work: send with `returnImmediately` or stream, then poll `GetTask`. A bot reaches another A2A agent with `message_bot` and a `peers` entry `a2a:<url>`; its bearer for that agent is the Connections credential `a2a:<host>` (or `a2a:<host>/<path>` for one door on a host).

## 8.7 Versions

Versions are explicit snapshots (`workflow-store.ts`):

- `save_version` copies the **current live YAML** into a new version numbered one above the highest so far (V1, V2, …), with an optional `version_description` note and a checksum. It becomes the workflow's current version.
- `list_versions` shows every snapshot: Version, ID (the `version_id`), When (ISO time), Note.
- `get_version` returns one snapshot's YAML without changing anything.
- `restore_version` writes that snapshot's YAML back as the live definition, then saves it as a **new** version with the note "Restored from V\<n\>". History is never rewritten, and you can always roll forward again.

`update` and `plungeai_build_workflow`'s refine both overwrite the live YAML without a snapshot. Save a version first so you can roll back.

## 8.8 Generate a workflow — `plungeai_build_workflow`

`plungeai_build_workflow` turns a plain-language goal into a real multi-agent workflow and saves it. It uses the platform's workflow builder, which picks actual registry agents, validates the YAML and repairs it itself.

| You send | Does |
|---|---|
| `goal` | Generates a **new** workflow and saves it. It is named from the generated YAML's own `name:` line (up to 120 characters), or from the first 60 characters of your goal when the YAML has no plain `name: value` line (a name containing a quote or apostrophe does not count as one), with the description "Generated via MCP builder", no folder, kind `workflow` |
| `workflow_id` + `instruction` | **Refines** an existing workflow: sends its current YAML plus your change to the builder, which regenerates the whole workflow, then overwrites it in place. The name stays; no version is saved |
| anything else | "Provide either `goal` (new) or `workflow_id` + `instruction` (refine)." (`isError`) |

If you send `goal` together with `workflow_id` but no `instruction`, a new workflow is generated and the id is **ignored**.

It takes 30 to 60 seconds, and it narrates while it works:

```
📖 Loading the existing workflow…                (refine only)
🧠 Generating CNL YAML — the builder picks real agents, validates and self-repairs (30-60s)…
🔍 Validating the generated workflow (2,431 chars)…
💾 Saving "Acquisition News Briefing" and syncing to all apps…
✅ Complete
```

The answer names the workflow and its id, says "Synced to all apps.", and, for a new one, "Run it with plungeai_execute_workflow.", followed by the full YAML in a code block. It fails with `isError` and saves nothing when:

- the workflow id is unknown ("Workflow not found: \<id\>");
- the builder returns no workflow ("Workflow builder did not return a workflow section.");
- the generated YAML fails CNL validation ("Generated workflow failed CNL validation: …", with the YAML attached so you can fix it by hand).

The tool has no `format` argument.

*Try asking: "Build me a workflow that scans a company for acquisition news and writes a briefing."* To file it, follow up with *"…and move it to Finance"* (`plungeai_workflow` `update` with `folder`). To design a workflow in a conversation first, *"Let's design a due-diligence workflow together"* goes to `plungeai_chat` ([Chat, memory & skills](/mcp/chat-memory-skills)).

This runs the builder model and saves a workflow (it costs credits; not run by the docs verifier):

```bash
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_build_workflow","arguments":{"user_request":"Build me a workflow that scans a company for acquisition news and writes a briefing.","goal":"Scan a company (given as input) for recent acquisition news from several web sources in parallel, then write a one-page briefing with sources."}}}'
```

To refine: `{"workflow_id":"<id>","instruction":"Add a step that checks SEC filings for the same company."}`. Run `save_version` first.

## 8.9 Start from a template — `plungeai_templates` `use`

`use` copies a template ([5.5](/mcp/discovery#55-workflow-templates--plungeai_templates-list-and-get)) into your account as a new workflow you can edit:

- The name is your `name`, otherwise "\<template name\> (copy)". A `bot` template keeps its own name.
- The template's kind (`agent` or `bot`) and its description carry over.
- `folder` files it with the same matching as `plungeai_workflow` `create`: no match means the workflow is created without a folder, with a ⚠️ note listing your folders.
- The template's usage count goes up by one, the same as in Studio.
- The YAML is copied as it is. It is not re-validated.

The answer: "Created workflow **\<name\>** (`<id>`) from template **\<template\>**. Filed under **\<folder\>**. Run it with plungeai_execute_workflow."

*Try asking: "What workflow templates are available? Start one from the due-diligence template."*

This creates a workflow in your account (not run by the docs verifier):

```bash
curl -s https://mcp.plungeai.com/v1 \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_templates","arguments":{"user_request":"Start one from the due-diligence template","action":"use","template_id":"<template id from list>","name":"Due Diligence — Acme","folder":"Finance"}}}'
```

## 8.10 CNL essentials for ad-hoc YAML

The full language reference is the `plungeai-workflows` skill (https://skills.plungeai.com). The same guide is served to your client as the `plungeai://docs/workflow-authoring` resource ([Prompts & resources](/mcp/prompts-resources)). It is also summarised at https://api.plungeai.com/docs#101-cnl-in-one-page. The short version (`cnl-spec.md`, engine v6):

- **Shape.** `workflow: {name, tasks, inputs?}` (Form A) or bare `name` + `tasks` at the top level (Form B). Both are accepted. The spec's Form B top-level `input:` is not read over MCP: the gateway nests the whole Form B document under `workflow:`, where nothing reads `input`. Pass the value as the tool's `input` argument instead, or declare `inputs:` inside a Form A `workflow:` block.
- **Ten task types.** `task` (one agent: `agent` + `prompt` or `query`), `parallel` and `sequential` (`subtasks`), `dynamic` (`generator` + `executors`), `batch` (`tasks` over `items` / `items_from`), `output`, `debate`, `validate`, `agent_loop` (legacy), `harness` (a mission: `goal` + `mission` or `mission_ref`).
- **Interpolation.** `{input}`, `{key}` from `inputs`, `{data:task_id}` for a prior task's result, `{item}` / `{item.field}` inside a batch.
- **Hard rules.** Agent ids exactly as in the registry. No `$variable`, `depends_on`, `outputs:` or `parallel: true`. Container blocks never carry `agent:`. `type: task` reads `prompt:`, not `input` or `instructions`. Unique `id`s.

A mission as YAML: a `type: harness` task with the full mission fields, including experts, plugins, MCP servers, effort, spend cap and permissions, which `plungeai_run_mission` does not expose ([7.7](/mcp/agents-missions#77-mission-bounds)):

```yaml
name: Vendor scan
tasks:
  - type: harness
    id: market_scan
    goal: "Map the top 5 vendors for {input}, with pricing and one differentiator each."
    mission:
      mission: "You are a market analyst. Research thoroughly, cite sources, stay on topic."
      effort: standard
      max_turns: 12
      allowed_tools: [web_search, web_fetch, task_complete]
      success_criteria:
        - "5 vendors named with pricing"
        - "every claim has a source URL"
```

Run it with `plungeai_execute_workflow`, passing the YAML as `workflow_yaml` and the value for `{input}` as the tool's `input` argument (for example `"input":"workflow orchestration platforms"`).
