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

# Workflows — CNL orchestration: what it is and how to run it

> A workflow is a YAML document (CNL — Cognitive Natural Language) that the engine executes as a DAG of agent calls: parallel fan-out, sequential pipelines, unlimited nesting, conditions, batches, debates, and bounded autonomous missions.


<!-- sources-of-truth: orchestration/cnl-engine/README.md, orchestration/cnl-engine/schema-types.ts, orchestration/api-gateway/openapi.ts, orchestration/mcp-gateway/server.ts, apps/ocean-skills/skills/plungeai-workflows/SKILL.md | last-synced: 2026-09-24 -->
A **workflow** is a YAML document (CNL — Cognitive Natural Language) that the engine
executes as a DAG of agent calls: parallel fan-out, sequential pipelines, unlimited
nesting, conditions, batches, debates, and bounded autonomous missions. The engine is
pure orchestration — it routes tasks to agents over RPC and coordinates data handoff;
agents do the actual work.

> **Authoring belongs to this `plungeai-workflows` skill** — full CNL spec, validation
> rules, recipes, and runnable examples ([`references/cnl-spec.md`](/skills/plungeai-workflows/references/cnl-spec), `recipes.md`,
> `examples/`). This reference is the platform-level view: what workflows can do,
> the facts that shape good designs, and every way to execute one. Do not write
> non-trivial YAML without the authoring skill loaded.

## Shape of a workflow

```yaml
name: Research and synthesize
tasks:
  - type: parallel
    id: research
    subtasks:
      - { type: task, id: web,  agent: brave-agent, query: "{input}" }
      - { type: task, id: deep, agent: exa-agent,   query: "{input}" }
  - type: task
    id: synthesize
    agent: llm-agent
    prompt: "Synthesize the research above into a brief on: {input}"
```

Data flows automatically: each task's result lands in SharedMemory and downstream
tasks receive upstream content — you never hand-wire results. Ten task types exist:

| Type | One line |
|---|---|
| `task` | Single agent call (the default) |
| `parallel` | All `subtasks` dispatched at once |
| `sequential` | `subtasks` in order, each seeing prior results |
| `dynamic` | Generator agent produces N items → executors fan out over them |
| `batch` | Per-item pipeline over a list — isolated; per-item tracking + resume is opt-in `track: true` |
| `debate` | N debaters, up to 5 rounds, optional judge |
| `validate` | Macro → parallel PASS/FAIL validators + judge verdict |
| `output` | Deliver a previous result (email/document) with a format transform |
| `harness` | Bounded autonomous mission — see `plungeai-missions` |
| `agent_loop` | Legacy alias for `harness` — prefer `harness` |

Blocks (`parallel`/`sequential`/`batch`/`dynamic`/`debate`/`validate`) never carry
`agent:` themselves; agents go on inner tasks. Conditions (`condition:` CEL, e.g.
`input.contains('urgent')` or `qa_gate.verdict == "FAIL"`) gate any task or block
before dispatch.

## Parallelism — the facts that should shape your designs

These are structural (read from engine code) and measured (from the engine's own
server-side event timestamps):

- `type: parallel` dispatches **every** subtask at once — one `Promise.all`, no
  chunking, no width cap. Each child is a **separate Worker invocation** over RPC.
- Engine overhead is **zero at every width** measured (1 → 100 children): dispatch
  spread 0 ms; workflow total equals the slowest child to the millisecond. A parallel
  block costs *the slowest of N*, not N of anything. The same 12 search tasks:
  sequential 18.9 s, parallel 1.9 s.
- **I/O-bound fan-out is flat** — children waiting on networks do not contend, even
  against the same agent service.
- **CPU-bound or isolate-heavy children queue at the destination**, not in the
  engine. Practical width against ONE shared destination: **~12-50** depending on the
  service; past that you are queueing, not gaining. To go wider, spread children
  across distinct destinations (a real research fan-out across six search providers
  does this naturally).
- Every child's result is durable in SharedMemory — collect at the end or pick up
  each as it lands.
- Measure with an A/B (`sequential` vs `parallel` of the same tasks), never by
  dividing a parallel total by a single-task average.

## Inputs and placeholders

`{input}` is the primary input; `{input1}`…`{input10}` and any custom
`{semantic_name}` also work — declared under `inputs:` and replaced in one pass.

**Date tokens** (for rolling windows — scheduled runs especially): `{now}`, `{today}`,
`{yesterday}`, `{week_start}`, `{week_end}`, `{last_week_start}`, `{last_week_end}`,
`{month_start}`, `{month_end}`, `{last_month_start}`, `{last_month_end}`. All UTC,
ISO weeks (Monday first), seeded from the run's execution time; a caller-supplied
input of the same name wins. See `plungeai-scheduling` for the pattern.

**Search-query guard:** search agents cap query length (per their cards) and an
over-limit query — typed or auto-filled from a prior task's output — returns a
structured `needs_input` refusal; nothing is silently truncated. Give search tasks
short explicit queries; bulk upstream data flows as context, never as the query.

## Executing a workflow

### MCP (preferred when operating live)

```
# Ad-hoc (test before saving — always)
plungeai_execute_workflow {workflow_yaml: "<yaml>", input: "solid-state batteries"}

# Long run
plungeai_execute_workflow {workflow_yaml: "<yaml>", input: "…", mode: "async"}
#   → execution_id → plungeai_get_workflow_status → plungeai_get_result

# Saved workflow by id
plungeai_execute_workflow {workflow_id: "…", input: "…"}
```

YAML gotchas the platform will refuse: hard-wrapped values (use block scalars
`prompt: |`), unquoted values containing colons, invented fields (`depends_on`,
`outputs:`, `parallel: true`, `schedule:` — scheduling is a separate system, see
`plungeai-scheduling`). Or skip hand-writing: `plungeai_build_workflow {goal}`
generates and saves one via the platform builder.

Manage saved workflows with `plungeai_workflow`
(`action: create|get|update|delete|save_version|list_versions|get_version|restore_version`)
— create/update/delete sync live to Studio and peer apps. Create/update also take
`folder` (file/move it — `"none"` clears) and `kind: workflow|agent|bot` (which
sidebar section it lives in: Flows, Agents, or Bot agents). This is the mechanic
behind the "my agents" terminology: a saved item with `kind: agent` IS the user's
"agent", and `plungeai_list_workflows` filters by `folder`. Server-side validation
on save is your final gate: a refusal lists field errors; fix exactly those.
Pre-built starting points: `plungeai_templates {action: list|get|use}`.

### One API

```bash
# Inline (JSON wrapper or raw text/yaml body)
curl -s -X POST https://api.plungeai.com/v1/workflows/execute \
  -H "Authorization: Bearer ozk_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"workflow": "name: quick\ntasks:\n  - agent: brave-agent\n    query: \"{input}\"\n", "input": "latest AI news"}'
# → {success, workflow_id, final_task_id, request_id}

# Saved workflow by id (resolved against your user)
curl -s -X POST https://api.plungeai.com/v1/workflows/{id}/execute \
  -H "Authorization: Bearer ozk_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"input": "latest AI news"}'

# Live progress: SSE stream of engine events
curl -N -X POST https://api.plungeai.com/v1/workflows/{id}/execute-stream \
  -H "Authorization: Bearer ozk_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"input": "…"}'
```

The acknowledgement is a pointer, not the content. Redeem results:

```bash
curl -s https://api.plungeai.com/v1/workflows/results/{workflowId}/{taskId} \
  -H "Authorization: Bearer ozk_YOUR_KEY"
# 200 {content, content_type, workflow_id, task_id} | 404 not_ready (still running)
```

`final_task_id` from the acknowledgement is the task id of the final result; any
intermediate task id works too (read one branch of a fan-out). Errors:
`400 missing_workflow`, `404 workflow_not_found`, `502 engine_error`.

### Studio

Humans edit and run workflows visually; the Code tab accepts pasted YAML with inline
validation. Point users there for visual work and credential connects.

## Reading results

- MCP: `plungeai_get_result {workflow_id}` → final output (full conversation thread
  for conversational runs); add `task_id` for a single step. Output is final
  user-ready markdown — relay verbatim.
- One API: the `/results/` endpoints above.
- Execution completed but "results not yet available" → retry `plungeai_get_result`;
  storage is eventually consistent by a beat.

## Follow-ups

A completed execution can be continued conversationally:
`plungeai_followup {execution_id, prompt: "<follow-up question>"}` — `prompt` is the
required parameter (`message` belongs to `plungeai_continue`, the HITL resume tool —
do not cross them). It reuses the run's context. Workflows
may carry a `followup:` block (provider/model/memory_scope/temperature/max_tokens)
which the engine ignores at execution time; it configures this follow-up behavior.
Full conversation/HITL detail: `plungeai-results-traces`.

## Failure semantics worth knowing

- Parallel branches are best-effort: a failed branch does not abort the run; the
  summary carries `failed_branches[]` (present only when something was lost). Check
  it before trusting a synthesis over fan-out results.
- Per-task `retry: N` (max 2) exists for plain `type: task` only — never for
  `harness` — and re-fires the FULL agent call: set it only on read-only/idempotent
  agents, never payment/messaging/automation classes.
- The execution summary (`total_tasks_executed`, `tasks_skipped`,
  `parallel_blocks_executed`, per-task durations) is your first debugging read — see
  `plungeai-results-traces`.
