plungeai-workflows
Workflows plane — /v1/workflows
Run multi-agent CNL workflows: inline (send the workflow document in the request), saved (reference a workflow id saved in Ocean Studio), each with an optional SSE streaming variant, plus a results-redemption route.
Run multi-agent CNL workflows: inline (send the workflow document in the request), saved (reference a workflow id saved in Ocean Studio), each with an optional SSE streaming variant, plus a results-redemption route.
Auth: Authorization: Bearer ozk_YOUR_KEY on every route here. Tenancy is
automatic — the engine keys every run to your key's identity; saved workflows
and results resolve only within it.
Authoring CNL YAML itself (task types, parallel/sequential, harness
missions) is this plungeai-workflows skill's job; this file covers the HTTP
surface for executing it.
POST /v1/workflows/execute — inline execution
Two body formats:
JSON — {"workflow": <object or YAML string>, "input"?: string, "inputs"?: object}:
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": "demo",
"tasks": [
{"type": "task", "id": "t1", "agent": "llm-agent", "prompt": "Say: ok"}
]
},
"input": "x"
}'Raw YAML — send the whole workflow document as the body with
Content-Type: text/yaml:
curl -s -X POST https://api.plungeai.com/v1/workflows/execute \
-H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: text/yaml" \
--data-binary @- <<'YAML'
workflow:
name: demo
tasks:
- type: task
id: t1
agent: llm-agent
prompt: "Say: ok"
YAMLResponse — HTTP 200 (an acknowledgement + pointer, not the content):
{
"success": true,
"workflow_id": "00000000-0000-4000-8000-000000000001",
"final_task_id": "t1",
"request_id": "00000000-0000-4000-8000-000000000004"
}Redeem the actual output with workflow_id + final_task_id (below). For a
multi-task workflow, final_task_id is the last task — intermediate task
results are redeemable by their own task ids.
Errors: 400 missing_workflow (no workflow in a JSON body / empty YAML),
502 engine_error (engine reported failure; message included),
404 workflow_not_found — yes, on the inline route too: any engine
failure whose message contains "not found" (typically an unknown agent id →
"Service binding not found") maps to 404. Re-discover the agent id; don't hunt
for a missing saved workflow.
POST /v1/workflows/{id}/execute — saved workflow
Runs a workflow saved under your account (the id shown in Ocean Studio).
Body is optional: {"input"?: string, "inputs"?: object}.
curl -s -X POST https://api.plungeai.com/v1/workflows/wf-morning-brief/execute \
-H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "today"}'{
"success": true,
"workflow_id": "00000000-0000-4000-8000-000000000001",
"final_task_id": "t3",
"request_id": "..."
}Errors: 404 workflow_not_found (no such id under your identity — list your
saved workflows in Studio, or re-check the id), 502 engine_error.
SSE streaming — live workflow events
Two variants, same event stream:
POST /v1/workflows/{id}/execute-stream— saved workflow (in the OpenAPI spec)POST /v1/workflows/execute-stream— inline body, same formats as/v1/workflows/execute, fully documented in the OpenAPI spec (orchestration/api-gateway/openapi.ts) alongside the saved-workflow variant. Caveat: this inline variant forwardsinputbut silently dropsinputs(the JSON branch never readsbody.inputs, unlike the YAML branch which does —orchestration/api-gateway/routes/workflows.ts) — need multi-input? Use/v1/workflows/executeor/v1/workflows/{id}/execute-stream(both forwardinputs).
curl -N -X POST https://api.plungeai.com/v1/workflows/wf-morning-brief/execute-stream \
-H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "today"}'Response is text/event-stream with named events — each frame is
event: <type> + data: <json>, and the data payload always carries
{workflow_id, execution_id, timestamp, type, data, elapsed_ms, duration_ms?}
(the event-specific fields live under data). Event sequence:
request_received → workflow_loaded → workflow_started
→ task_dispatched → task_completed (per task)
→ heartbeat (every ~25s during a task >20s — expect it,
or idle timeouts will kill long streams)
→ workflow_completed (success only; execution summary + metrics)
→ workflow_result (terminal, ALWAYS check its success field)On failure the engine emits workflow_error (error + progress counters) and
the terminal workflow_result carries success: false + error — code that
only checks for the workflow_result event name reports failed runs as done.
Other event types appear for the matching task types (parallel_start,
batch_progress, debate_*, …) — switch on the ones you need, ignore the
rest.
A body the engine can't parse returns HTTP 400 whose body is itself an SSE
error event — check the status before treating the body as a stream.
Consume with any SSE client (EventSource won't do — it's a POST; use
fetch + a stream reader in JS, or curl -N / httpx.stream elsewhere) and
parse the event: lines, not just data: lines. The stream is pass-through
and unbuffered; x-request-id is stamped on the response headers. Don't rely
on a dropped stream's run surviving — the engine runs the workflow inside the
stream; for durability use /v1/workflows/execute + the results route.
GET /v1/workflows/results/{workflowId}/{taskId} — redeem a task result
curl -s https://api.plungeai.com/v1/workflows/results/00000000-0000-4000-8000-000000000005/t1 \
-H "Authorization: Bearer ozk_YOUR_KEY"Ready — HTTP 200:
{
"content": "ok",
"content_type": "text/plain",
"workflow_id": "00000000-0000-4000-8000-000000000001",
"task_id": "t1"
}Not ready — HTTP 404 {"error":{"code":"not_ready",...}}. Poll with backoff
(2s+, growing); not_ready while a workflow runs is normal.
POST /v1/workflows/executions/{id}/cancel — cancel a running execution
Cooperative cancel: writes the SharedMemory cancel marker the engine and agent
loops poll at their next turn/tool boundary, then stamps the execution row
cancelled. {id} is the execution id — the same value returned as
workflow_id from /v1/workflows/execute (or the x-trace-id you sent on
that call). Since /v1/workflows/execute is synchronous, send the cancel from
a second connection while the first is still running.
curl -s -X POST https://api.plungeai.com/v1/workflows/executions/00000000-0000-4000-8000-000000000005/cancel \
-H "Authorization: Bearer ozk_YOUR_KEY"{
"success": true,
"cancelled": "00000000-0000-4000-8000-000000000005",
"markers": ["00000000-0000-4000-8000-000000000005"]
}Errors: 404 not_found (no such execution under your identity — wrong owner
never confirms the execution exists), 409 not_running (already finished or
already cancelled).
Fire-and-redeem pattern (Python)
import time, requests
BASE = "https://api.plungeai.com"
H = {"Authorization": "Bearer ozk_YOUR_KEY"}
ack = requests.post(f"{BASE}/v1/workflows/execute", headers=H, json={
"workflow": {"name": "demo", "tasks": [
{"type": "task", "id": "t1", "agent": "llm-agent", "prompt": "Say: ok"}]},
}).json()
wf, task = ack["workflow_id"], ack["final_task_id"]
for i in range(1, 9):
r = requests.get(f"{BASE}/v1/workflows/results/{wf}/{task}", headers=H)
if r.status_code == 200:
print(r.json()["content"]); break
time.sleep(2 * i)Trace correlation
Send x-trace-id: <your-id> on any execute call; every engine span of the run
(dispatches, completions, the result) lands under that id at
GET /v1/traces/<your-id> — see plungeai-results-traces.
Legacy aliases (deprecated — do not use in new code)
POST /v1/execute, POST /v1/cnl/execute, POST /v1/cnl/execute-stream are
pre-router aliases of the inline workflow handlers, kept so old integrations
don't break. Same auth, same bodies, same responses. New and generated code
targets /v1/workflows/* only.