//DOCS Workflows

Workflow IR, interpreter, builder, scheduling, credentials, and endpoints.
Open App

Workflows

Punk workflows are multi-step agent jobs (classify, fetch, branch, call tools, notify) defined as a graph, executed by a deterministic interpreter, and optimized by the same runtime loop that optimizes single chat requests.

Design Rules

  • Workflows are interpreted, never code-generated. The graph is data; a deterministic interpreter walks it one node at a time. There is no prompt-to-code compiler and no generated JavaScript to audit.
  • LLM nodes are real gateway runs. Every llm node goes through Punk's router, so it inherits governance, caches, artifact routing, fingerprinting, and the learning loop.
  • Node traces reuse the trace ledger. A workflow run owns its workflow.node.* events, integrity-chained like every other run.
  • Scheduling reuses durable jobs. A minute-cadence sweep reads the workflows table fresh each pass, so deleting a workflow always deletes its schedule.

The Workflow IR

A workflow's graph is a WorkflowIR: { version: 1, entryId, nodes }. Graphs are DAGs: cycles and unreachable nodes are rejected at create/update/import time and again before every run.

Nodes read and write a shared variable environment seeded with the run input (also available as input). Expressions (IRExpr) are the only way config reaches that environment:

KindShapeMeaning
literal{ "kind": "literal", "value": ... }A constant.
ref{ "kind": "ref", "path": "triage.json.priority" }Dotted path into the vars environment.
template{ "kind": "template", "template": "{{page.context}}" }String interpolation over vars.

Node Kinds

KindConfigWhat it does
start(none)Entry marker; follows next.
llmpromptTemplate (required), system?, model?, saveAs?Renders the templates over vars and executes a real gateway chat run. Saves {content, json, runId, route, costUsd, savedUsd} to vars[saveAs ?? "llm"]; json is a best-effort parse of the content.
tool_callserverId (required), tool (required), args? (map of name → IRExpr), saveAs?Invokes a tool on a registered external MCP server. Governance-checked first; see below.
web_fetchurlExpr (IRExpr, required), saveAs?Semantic web fetch. Saves {context, tokensSavedEstimate} to vars[saveAs ?? "page"], compact SOM context instead of raw HTML.
web_acturl (required), intents (required)Reserved in v1: the node fails cleanly with web_act not wired. Governed web sessions land here later.
choicebranches (required), otherwise?Branches evaluated in order; first match wins, else otherwise, else next, else the run ends. Conditions are a full IRExpr or the shorthands contains: [path, needle], eq: [path, value], truthy: path.
set_varname (required), value (IRExpr, required)Assigns into the vars environment.
transformexpr (IRExpr, required), pick? (string[]), saveAs?Evaluates the expression, optionally picking keys from an object result.
notifyurl (required, http/https), payloadTemplate?POSTs JSON to a webhook (rendered template parsed as JSON when possible, else {message}). Same SSRF policy as webhook delivery: private destinations always blocked. 5s timeout.
outputvalue (IRExpr, required)Terminates the run with the value.

Execution Limits

  • 100 nodes per run (also the cycle backstop), 120s wall clock.
  • A node failure fails the run. There are no per-node retries; scheduled runs retry whole-run through the job queue (up to 3 attempts), manual/API runs never retry.

Interpreted, Never Code-Generated

Workflows follow the same principle as artifacts: a declarative structure executed by an interpreter, never generated or eval'd code. The interpreter itself is IO-free: every effect (LLM calls, MCP tools, web fetches, webhooks, tracing) is injected by the gateway, which is what makes runs deterministic, testable, and traceable node by node.

LLM Nodes Are Gateway Runs

This is the optimization story. Each llm node is a loopback request into Punk's own router with app id workflow:<id> and agent workflow-engine, temperature 0. That means:

  • The node is governed, fingerprinted, and traced like any other run.
  • Repeated node work hits the exact and semantic caches, forms patterns, and synthesizes artifacts. The workflow gets cheaper run over run without changing the graph.
  • Per-node cost and savings are read from the persisted child gateway run and roll up onto the workflow run's costUsd/savedUsd.

GET /api/v1/workflows/:id/savings aggregates this across run history: total cost, total saved, and optimizedShare, the share of would-have-been spend the optimizer avoided.

Builder UI

#/workflows is the built-in visual workflow creator. Start here when you want a multi-step agent job without writing code.

The fastest path:

  1. Open the template gallery.
  2. Pick support-triage, web-research, or pricing-monitor.
  3. Click USE TEMPLATE.
  4. Click RUN.
  5. Paste JSON input and inspect the returned output, cost, savings, and run timeline.

After that, edit the graph. #/workflows lists workflows (enabled toggle, node count, cron badge, last run, per-workflow savings) plus the template gallery. #/workflows/:id is a three-pane editor:

  • Palette: click or drag node-kind chips onto the canvas.
  • Canvas: drag nodes, drag the background to pan, click a node's output port then a target to connect (choice sources add a branch). DEL removes the selected node/edge, ESC cancels a connect, AUTO-LAYOUT does left-to-right topological layering.
  • Inspector: per-kind config forms; IRExpr fields are kind+value selects. Validation errors from a rejected save render inline.

Toolbar: SAVE (dirty indicator), RUN (JSON input panel, synchronous result with a link to the run timeline), EXPORT (downloads the export envelope), DELETE.

Use Agents instead of the workflow editor when the job is just one scheduled prompt. Use Workflows when you need multiple nodes, branching, web fetches, tool calls, notifications, or export/import.

Run Timelines And Node Traces

Every node emits workflow.node.started, then workflow.node.completed ({durationMs, summary}) or workflow.node.failed ({error}) onto the workflow run's ledger. These events are integrity-chained per run, exactly like gateway runs, so node timelines are tamper-evident.

GET /api/v1/workflow-runs/:id returns the run plus its events. The dashboard renders this as a node timeline at #/workflow-runs/:id; llm node payloads link through to the child gateway run, where the route explanation says whether the node was served live, from cache, or by an artifact.

Scheduling

Set scheduleCron to a 5-field cron expression (UTC). Supported syntax: , numbers, lists (1,15,30), ranges (9-17), steps (/5, 10-50/10). Deliberate limits: no month/day names, no L/W/#/? extensions. When both day-of-month and day-of-week are restricted, the date matches if either matches (standard cron).

How it runs: a minute-cadence sweep job reads the scheduled workflows fresh each pass and enqueues a durable run job per match, deduped per workflow per UTC minute. There are no persistent repeatable jobs to clean up. Deleting (or disabling) a workflow is also unscheduling it, with no orphan schedule left behind.

Credentials

Stored credentials hold the secrets that workflow tool calls and MCP servers need:

  • Encrypted at rest with AES-256-GCM inside the store boundary. Key: PUNK_ENCRYPTION_KEY (32 bytes, base64). Unset, Punk derives a deterministic dev key and warns loudly. Set a real key in production.
  • The API never returns a secret in any form. POST /api/v1/credentials accepts the secret once and responds with a masked record; lists are masked too.
  • Reference a credential as cred:<id> in MCP server env/header values. It resolves at connect time, tenant-checked, inside the store.

Tool Calls And Governance

A tool_call node executes against a registered external MCP server. The full MCP usage guide (registering stdio/http servers, connection testing, cred:<id> references, node config examples, and troubleshooting) is Runtime Engine § Using MCP Servers From Punk; the reverse direction (driving these workflows from Claude) is § Using Punk From Claude. Before any connection is made, the gateway classifies the tool's side-effect level and runs the governance check with the workflow's agent identity. Both deny and approval_required fail the node closed. An unattended workflow cannot wait on a human. tool.called/tool.completed events land on the workflow run's ledger with the classified side-effect level.

web_fetch nodes use the same SSRF posture as POST /api/v1/web/fetch; notify nodes use the stricter webhook posture (private destinations always blocked).

Export And Import

POST /api/v1/workflows/export returns a { "punkWorkflows": 1, "workflows": [...] } envelope (all workflows, or pass ids to select). POST /api/v1/workflows/import validates every entry before creating anything. Imports are atomic, and a single invalid graph rejects the whole batch with per-entry errors.

Endpoints

Mutations require admin except the read-only export endpoint. See API for auth and response conventions.

MethodPathPurpose
GET/api/v1/workflowsList workflows.
POST/api/v1/workflowsCreate (graph validated; 400 with per-node errors).
GET/api/v1/workflows/:idRead one.
PATCH/api/v1/workflows/:idUpdate; every accepted PATCH bumps version.
DELETE/api/v1/workflows/:idDelete (also unschedules).
POST/api/v1/workflows/:id/runExecute synchronously; body { input?, trigger? }; 422 if the stored graph is invalid.
GET/api/v1/workflows/:id/runsRun history (?limit=).
GET/api/v1/workflows/:id/savingsCost/savings rollup with optimizedShare.
GET/api/v1/workflow-runs/:idRun plus its node-level trace events.
GET/api/v1/workflow-templatesBuilt-in templates.
POST/api/v1/workflow-templates/:id/instantiateCreate a workflow from a template ({ name? }).
POST/api/v1/workflows/exportExport envelope ({ ids? }).
POST/api/v1/workflows/importAtomic import of an export envelope.
GET/api/v1/credentialsList stored credentials (masked).
POST/api/v1/credentialsStore a credential ({ name, provider, secret }); secret never echoed.
DELETE/api/v1/credentials/:idDelete a credential.

Templates

Built-in templates ship complete, validated graphs and run end-to-end offline against the mock provider:

TemplateWhat it doesInput
support-triageClassify a ticket with an LLM node, branch on priority, webhook high-priority tickets, emit the classification.{ ticket: { subject, description } }
web-researchFetch a page as compact SOM context, summarize it with an LLM node, emit the summary.{ url }
pricing-monitorFetch a pricing page as SOM, extract plans and prices, emit the extraction. Pair with a cron schedule to watch competitors.{ url }

Instantiate from the dashboard gallery or POST /api/v1/workflow-templates/:id/instantiate.