For AI agents: a documentation index is available at https://www.plungeai.com/docs/llms.txt. Append .md to any page URL, or send Accept: text/markdown, to get markdown. Setup instructions for agents are at https://www.plungeai.com/docs/agents.md. One ozk_ key opens every plane, models included.

Documentation Index: fetch the complete documentation index at /llms.txt. Use this file to discover all available pages before exploring further.

The workflows plane runs a CNL workflow — one or many agent tasks orchestrated by the engine — and hands back a pointer to the result. Reach it with an ozk_ key (Authentication) at POST /v1/workflows/execute (inline) or POST /v1/workflows/:id/execute (saved). Base URL is always https://api.plungeai.com. Source: orchestration/api-gateway/routes/workflows.ts, engine orchestration/cnl-engine/.

Where the Agents plane runs one task, the workflows plane runs a whole DAG: parallel fan-out, sequential stages, data flow between tasks, and bounded autonomous type: harness missions.

10.1 CNL in one page

CNL (the YAML workflow language) has a workflow with a name and a tasks[] array. Each task is either a leaf task or a compound block; a top-level input: (sibling of workflow:) seeds the run. Field names are from orchestration/cnl-engine/context.ts, schema-types.ts and the real files in workflows/*.yaml.

FieldOnMeaning
nameworkflowworkflow name
tasks[]workflowordered list of tasks/blocks
typetasktask | parallel | sequential | harness | dynamic | batch | debate (the TaskType enum also has the legacy agent_loop and internal output / validate)
idtasktask id — the key its output is stored under
agentleaf taskthe agent id to run (must be status:active, or the run is fenced 404/403)
promptleaf taskthe task input; search agents take the literal query
operationleaf taska named operation for a structured tool-agent (e.g. serpapi-agent operation: amazon)
(agent params)leaf taskany extra card parameters as task fields (e.g. num: 15, searchType: shopping)
subtasks[]compound blockchildren of a parallel / sequential / … block

Data flow. There is no depends_on — a reference is the dependency:

  • {input} injects the workflow-level input.
  • {data:<task_id>} injects a prior task's stored output into an LLM prompt. A sequential block runs its subtasks in order; a parallel block runs them at once (Promise.all, no width cap — see the engine README "Parallelism"). Reference an upstream task and the engine orders around it.

A real 3-task workflow — a parallel search fan-out feeding one LLM synthesis (adapted from workflows/amazon-price-comparison.yaml):

workflow:
  name: price-check
  tasks:
    - type: parallel
      id: search_phase
      subtasks:
        - type: task
          id: amazon_search
          agent: serpapi-agent
          operation: amazon
          prompt: "{input}"
          num: 15
        - type: task
          id: shopping_search
          agent: serper-agent
          searchType: shopping
          prompt: "{input}"
    - type: task
      id: recommendation
      agent: one-agent
      prompt: |
        For the search "{input}", pick the top 3 products.
        Amazon results: {data:amazon_search}
        Shopping results: {data:shopping_search}
        Use only links that appear verbatim in the data.
input: "wireless headphones"

10.2 Inline execute

POST /v1/workflows/execute runs one body in three sizes (routes/workflows.ts): a bare agent call, a workflow: document, or the JSON twin of either. Content-Type decides YAML or JSON; both produce the same object.

A bare agent call — agent: plus the agent's own fields, exactly as its README documents them. The door lifts it to a one-task workflow ({ workflow: { name: <agent>, tasks: [{ id: "call", type: "task", agent, …fields }] } }; a pre-built agent becomes a harness task with the prompt as its goal). Top-level format:, input: and inputs: stay the wire's, as in every other body; stream:, sync:, dry_run: and metadata: are wire keys too and never reach the agent.

curl -s -X POST "https://api.plungeai.com/v1/workflows/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
agent: calculator-tool-agent
expression: "7*6"
format: markdown
EOF

The JSON twin is {"agent":"calculator-tool-agent","expression":"7*6"} → the JSON envelope below with the result in content. Two refusals guard the lift: agent: next to workflow:/tasks: → 400 ambiguous_body (send one); a field named like an engine task key (id, type, agent, depends_on, mission_ref) → 400 reserved_field naming the fields. An unknown agent id → 404 unknown_agent here (on POST /v1/execute, 16.9, the same body is 404 agent_not_found). Every execute door refuses a body it cannot read, on the negotiated wire: malformed JSON → 400 invalid_json (with the parser's position), malformed YAML → 400 invalid_yaml, a scalar or list root → 400 invalid_body; an empty body is missing (missing_workflow, missing_prompt, invalid_params), not malformed.

A params: wrapper. On the one door, POST /v1/execute (16.9), the fields may also come wrapped in params: {…}, the shape the tools plane uses: the door unwraps them into the agent's fields, and a key inside params wins over a bare field of the same name (routes/execute.ts). The wrapper carries agent fields only. params naming agent, workflow, tasks, on_behalf_of or a wire key (format, input, inputs, user_context, stream, sync, dry_run, metadata) is 400 invalid_body, because those keys stay at the root. A params that is not an object stays an ordinary field. Live-verified 2026-10-14:

curl -s -X POST "https://api.plungeai.com/v1/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
agent: calculator-tool-agent
params:
  expression: "7*6"
format: markdown
EOF
# → # Calculator: 7*6 … ## 2. Result 42

A workflow — the full workflow: document as a text/yaml body, or JSON { "workflow": <object | YAML string>, "input"?: any, "inputs"?: any }; a JSON body with top-level name/tasks auto-nests under workflow. A body with none of workflow, agent or tasks → 400 missing_workflow.

Every execute body — inline, saved (10.4) and both execute-stream variants — also takes:

FieldTypeMeaning
user_contextobject · optional · default: derived from your request's network location (time zone, approximate place)the caller's time zone, locale and location ({time_zone, locale, place, latitude, longitude, accuracy_m, source}, the UserContext schema); the agent uses it for local time and as the default schedule time zone. A wire key — it never reaches a bare agent call's fields. A raw YAML workflow body always takes the network default

Success carries the pointer and, once the sync run has stored it, the final task's content: { success: true, status, workflow_id, final_task_id, request_id, content? }. status is "completed", or "incomplete" when the final task stopped at its turn budget before task_complete — the stored result is then partial (the results route returns it with a [run stopped at the N-turn budget …] note), and the execution row carries outcome: incomplete. The result route (10.5) reads the same content back later, on any wire.

curl -s "https://api.plungeai.com/v1/workflows/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"workflow":{"name":"doc-demo","tasks":[{"type":"task","id":"t1","agent":"one-agent","prompt":"Reply with only the word: pong"}]},"input":"go"}'

The same run as a raw YAML body (the response mirrors the request Content-Type → YAML, see 10.6):

printf 'workflow:\n  name: doc-demo-yaml\n  tasks:\n    - type: task\n      id: t1\n      agent: one-agent\n      prompt: "Reply with only the word: pong"\n' \
| curl -s "https://api.plungeai.com/v1/workflows/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @-

10.3 Streaming — execute-stream

POST /v1/workflows/execute-stream (same body shapes) forces the engine to stream and relays every engine SSE frame verbatim, plus a relay-injected event: token per provider delta (lib/stream-relay.ts workflowsEncoder). This is the named-event stream (distinct from the OpenAI-shaped agent stream in Streaming). Event vocabulary (context.ts CNLEventType):

EventWhenPayload (data:)
request_receivedrun acceptedrequest metadata
workflow_loadedworkflow parsedworkflow name/id
workflow_startedexecution beginsworkflow id
task_dispatcheda task is sent to its agent{task_id, agent, streaming?}
streaming_starteda task's provider stream opened{task_id, stream_url}
tokena provider delta (relay-injected){task_id, delta}
streaming_completeda task's provider stream closed{task_id}
task_completeda task finished{task_id, …}
task_errora task failed{task_id, error}
output_delivereda task output was stored{task_id}
parallel_start / parallel_completea parallel block boundaryblock id
sequential_start / sequential_completea sequential block boundaryblock id
batch_* / debate_* / dynamic_*block-specific progressblock payload
condition_evaluateda task condition was resolved{result}
heartbeatidle keepalive—
workflow_completedoutcome (success){final_task_id, execution_summary}
workflow_erroroutcome (failure){error}
workflow_resultlast frame, after the outcome{final_task_id, …}

The outcome frame is workflow_completed (or workflow_error); workflow_result is always the last frame, on success and on failure — a client that closes on the outcome frame misses it. Live-verified 2026-10-04 the stream carried: request_received, workflow_loaded, workflow_started, task_dispatched, task_completed, workflow_completed, workflow_result.

printf 'workflow:\n  name: stream-demo\n  tasks:\n    - type: task\n      id: t1\n      agent: one-agent\n      prompt: "Reply with only the word: pong"\n' \
| curl -sN "https://api.plungeai.com/v1/workflows/execute-stream" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @-

10.4 Saved workflows

A saved workflow has an id in the engine's KV (workflow:{user}:{id}). Create one in Studio or over the MCP plane (plungeai_build_workflow from a goal, or plungeai_workflow action: create). Then run it by id:

# not run here — requires a saved workflow id you own
curl -s "https://api.plungeai.com/v1/workflows/<workflow_id>/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"input":"quarterly numbers"}'

Body: { "input"?: any, "inputs"?: any }. An unknown id → 404 workflow_not_found. A streaming variant POST /v1/workflows/:id/execute-stream exists with the same body and the 10.3 event vocabulary.

10.5 Results route

GET /v1/workflows/results/:workflowId/:taskId → { content, content_type, workflow_id, task_id }, with the same reconciliation as the agents plane: 404 not_ready (in flight), 409 execution_failed, 422 empty_completion.

RESP=$(curl -s "https://api.plungeai.com/v1/workflows/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"workflow":{"name":"res-demo","tasks":[{"type":"task","id":"t1","agent":"one-agent","prompt":"Reply with only the word: pong"}]}}')
WF=$(printf '%s' "$RESP" | sed -n 's/.*"workflow_id":"\([^"]*\)".*/\1/p')
FT=$(printf '%s' "$RESP" | sed -n 's/.*"final_task_id":"\([^"]*\)".*/\1/p')
for i in 1 2 3 4 5 6 7 8; do
  R=$(curl -s "https://api.plungeai.com/v1/workflows/results/$WF/$FT" \
    -H "Authorization: Bearer $PLUNGE_API_KEY")
  case "$R" in *'"content"'*) echo "$R"; break;; *) sleep 2;; esac
done

10.6 Response formats

One negotiated payload, four wires (lib/format.ts): json (default), yaml, markdown, text. Precedence: body.format / ?format= → Accept header → the request Content-Type mirror (text/yaml → yaml, else json). An explicit unknown format value → 400 invalid_format; an unknown Accept type falls through to the mirror. markdown renders through the same renderer the MCP plane uses; text is the bare result content.

On the YAML wire the envelope fields travel inside the document: top-level format:, input: and inputs: next to workflow: in a text/yaml body (inline execute and execute-stream; the saved-workflow execute takes the same three in a YAML or JSON body) mean exactly what the JSON frame's fields mean — format has body precedence, input is substituted for {input}, inputs is the named map — and are stripped before the engine sees the workflow (routes/_util.ts splitRequestEnvelope). Every wire carries the final task's content once the sync execute routes have stored it: markdown and text answer with it as the body (the pointer keys still render when it is not stored yet); json and yaml add it as content next to the unchanged pointer fields.

10.7 The type: harness task

A type: harness task with agent: one-agent binds an inline mission onto one-agent's loop mode — a bounded ReAct agent with a tool fence, iteration cap, long-term memory, and capability injection. All fields and a full example are in Skills, plugins & capability injection; in short:

workflow:
  name: research-brief
  tasks:
    - type: harness
      id: brief
      agent: one-agent
      goal: "One-paragraph brief on the latest Anthropic model; every claim a source URL."
      mission: "You are a research analyst. Produce sourced, factual briefs."
      allowed_tools: [web_search, web_fetch, task_complete]
      success_criteria:
        - "Every factual claim is followed by a source URL."
      max_iterations: 8
      skills: [research]

10.8 Legacy aliases, limits & headers

Deprecated aliases (same auth, same workflow-plane handlers; prefer /v1/workflows/*): POST /v1/cnl/execute, POST /v1/cnl/execute-stream (api-gateway.ts).

POST /v1/execute is not an alias. It is the Agent Call door, level 3 of the API (The one door). It still takes every inline body of 10.2 (a workflow:, root tasks:, a bare building-block agent) and adds agent: <saved id>[@n], dry_run and stream. An unknown agent: there is 404 agent_not_found; a task inside a workflow: body that names an unknown agent is still 404 unknown_agent (below). The level 2 routes of this chapter are unchanged.

Limits (see Errors, limits & headers for the full table):

  • Body cap: MAX_REQUEST_SIZE, default 1 MiB → 413 payload_too_large.
  • Replayed x-trace-id UUID → 409 duplicate_execution_id (send a fresh UUID or omit the header).
  • parallel fan-out has no engine-side width cap (Promise.all); throughput is bounded by the downstream agent service, not the engine.
  • 400 invalid_workflow for a malformed CNL document; 404 unknown_agent / 403 agent_not_active when a task names an unknown or non-active agent (fenced before the run opens).
  • Every response carries x-request-id (= the trace id); async/YAML/markdown responses also carry X-Execution-Id. See Traces & observability.

10.9 Cancelling a run

POST /v1/workflows/executions/:id/cancel stops a running execution. Cancellation is cooperative (core/agent-core/cancel.ts, routes/workflows.ts): it writes a marker in SharedMemory that the engine reads between tasks and the harness loop reads at the start of each turn and before each tool batch, then stamps the row cancelled. An in-flight model or tool call is not interrupted — the run stops at the next boundary, so cancellation may lag one turn.

Because POST /v1/workflows/execute is synchronous (it returns when the run is done), cancel is for runs you can reach from another request. Pass your own x-trace-id (a UUID) on the execute call — it becomes the execution id — and use that id here from a second client.

ResponseMeaning
200 { success: true, cancelled: <id>, markers: [<id>, …] }Marker written under each run id (row id + the engine's cln id when it differs); row stamped cancelled.
404 not_foundNo such execution for this key. A run owned by another key answers 404 too — the API never confirms a foreign run exists.
409 not_runningThe execution already finished, failed, or was cancelled.

This example mutates state (it cancels a run), so it is not auto-verified:

# Start a run with a trace id you choose, then cancel it from elsewhere with that same id.
TRACE=$(uuidgen)
curl -s "https://api.plungeai.com/v1/workflows/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -H "x-trace-id: $TRACE" \
  -d '{"workflow":{"name":"cancel-demo","tasks":[{"type":"task","id":"t1","agent":"one-agent","prompt":"Count slowly to a thousand."}]}}' &

curl -s -X POST "https://api.plungeai.com/v1/workflows/executions/$TRACE/cancel" \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
# → {"success":true,"cancelled":"<TRACE>","markers":["<TRACE>"]}

Over MCP the same action is plungeai_executions with action: "cancel" and the execution_id (orchestration/mcp-gateway/tools.ts): a foreign or unknown id answers Execution not found., a finished run answers that it is not running, and a running one is stopped at its next boundary.

Planned: TI-34

The docs assistant is not available yet. You can hand these docs to your own assistant instead.