LLMSkillHub
mirroredCopied from intelligencedev/manifold under MIT. intelligencedev has not claimed this page, and is not affiliated with LLM SkillHub.

intelligencedev / warpp-workflow-authoring

Author WARPP workflows for Manifold as typed-port dataflow JSON. Use when a user asks to build, create, design, generate, or edit a Flow / workflow / automation / pipeline / DAG in Manifold, or mentions WARPP, workflow nodes, or the /flow builder. Covers the document format, port types and coercions, the builtin node catalog, control flow (if/coalesce/map), and the workflow_catalog then workflow_save validate-and-self-correct loop.

0
SKILL.md
8.1 KB

Authoring WARPP Workflows

WARPP is Manifold's typed-port dataflow engine. A workflow is a directed
acyclic graph of nodes. **Data moves only through typed wires or literals —
there are no expressions.** Your job is to produce a valid workflow document
(JSON) and prove it valid with workflow_save.

The one rule

Every node is a pure function with declared, typed input and output ports. An
input binding is exactly one of:

  • {"from": "nodeID.port"} — a wire from an upstream node's output port
  • {"value": <literal>} — a fixed literal typed into the node

Never both. Never an expression string, template, or ${...}. To reshape data,
use a data node (data.extract, data.template, data.parse, …).

The authoring loop (do this every time)

  1. Discover tools with tool_search. For every step that performs an
    external action (web search/fetch, file I/O, calling an API or another
    agent, sending a message, RAG, an MCP capability, …), call tool_search
    with a natural-language description of the capability you need — e.g.
    "search the web", "read a file", "query a database",
    "send a slack message". The user may have configured MCP servers that
    publish tools you cannot know ahead of time, so **do not assume a tool
    exists or guess its name** — search, then pick the best-matching result for
    the step. If several match, choose the most specific/appropriate one.

  2. workflow_catalog — fetch the live node manifests for the tools you
    chose (plus the builtins), their exact port names/types, and the coercion
    table. Every registry tool is available as a node typed tool.<name>.
    Never guess a node type or a port name — read them from the catalog.

  3. Build the document (shape below). Prefer wiring (from) over literals
    for anything that should be dynamic.

  4. workflow_save — it validates and returns diagnostics. If any come
    back, fix each one (they carry a code, message, and path) and save
    again. Do not stop until it saves with no error diagnostics.

  5. workflow_run (only if asked to test) — runs it and returns the
    declared outputs.

Discover → build → validate → self-correct. Do not hand back an unvalidated
document, and do not invent a tool you did not find via tool_search or the
catalog.

Document shape

{
  "id": "research-brief",
  "name": "Research brief",
  "inputs": [{ "name": "topic", "type": "text", "required": true }],
  "nodes": [
    { "id": "search", "type": "tool.web_search",
      "inputs": { "query": { "from": "in.topic" }, "max_results": { "value": 5 } } },
    { "id": "summary", "type": "llm.generate",
      "inputs": { "instruction": { "value": "Summarize as 5 bullets." },
                  "input": { "from": "search.results_text" } } }
  ],
  "outputs": { "brief": { "from": "summary.text" } }
}
  • id matches ^[a-zA-Z0-9_-]+$; name is human-readable.
  • inputs are the workflow's typed parameters, exposed to nodes as
    in.<name> (e.g. {"from": "in.topic"}).

  • Each node has a unique id, a type from the catalog, and an inputs map
    keyed by the manifest's input port names.

  • outputs maps result names to node output ports; this is the run result.
  • in and item are reserved node ids.
  • Optional: settings.max_concurrency (default 4); publish.tool: true to
    expose the workflow as an agent tool; project_id to scope file tools to a
    project.

Types and coercions

Port types: text, number, boolean, json, file, list<T> (T is any
scalar). The only implicit coercions, applied to wires, are:

  • number → text
  • boolean → text

Nothing else coerces. json → text needs data.stringify; text → json needs
data.parse; pull a field out of json with data.extract. **Literals must
match the port type exactly** ("5" for a text port, 5 for a number port). A
file port accepts a literal path string.

Builtin nodes (stable; always available)

TypeInputsOutputs
data.extractsource: json, path: text, as: text (default "json")value (typed by as)
data.templatetemplate: text, vars (named, any type)text: text
data.mergeobjects (list of json)json: json
data.stringifyvalue: Ttext: text
data.parsetext: textjson: json
data.constantvalue: json, as: text (default "json")value (typed by as)
logic.ifcondition: boolean, value: Tthen: T, else: T (one fires)
logic.coalescevalues (list of T)value: T (first that fired)
logic.equalsa: T, b: Tresult: boolean
logic.containshaystack: text, needle: textresult: boolean
logic.notvalue: booleanresult: boolean
logic.greater_thana: number, b: numberresult: boolean
control.mapitems: list<T>, concurrency: number, on_item_error: textresults: list<…>
llm.generateinstruction: text, input: text, model: texttext: text

Tool nodes are tool.<name> — one for every tool in the registry,
including MCP-server tools (e.g. tool.web_search, tool.file_write,
tool.brave_brave_web_search). The set is instance-specific and not known in
advance, so find tools with tool_search (by capability) rather than
assuming; then use the match as node type tool.<its name>. Each tool node has
a result: json output plus, for curated tools, typed outputs. Get the exact
input/output ports from workflow_catalog.

data.extract / data.constant set their output type via the as config:
one of text, number, boolean, json, list<json>.

Named-variadic and list-variadic ports

  • data.template.vars is named-variadic: an object of bindings, one per
    {placeholder} in the template —
    "vars": { "topic": {"from":"in.topic"}, "n": {"value": 3} }.

  • logic.coalesce.values and data.merge.objects are list-variadic: an
    array of bindings — "values": [ {"from":"a.text"}, {"from":"b.text"} ].

Control flow (gated wires, not conditionals)

  • logic.if fires exactly one of then / else per run based on
    condition.

  • A node whose required input never fires (e.g. it reads the branch that
    didn't fire) is skipped, and skips cascade downstream. An optional
    input whose source is skipped falls back to its default.

  • logic.coalesce rejoins branches: it emits the first of its inputs that
    actually fired.

  • control.map fans out over items: list<T>: its body subgraph runs once
    per item, seeing item.value and item.index, and gathers the body's single
    result output into results. on_item_error is fail (default) or
    skip. Bodies may also wire from outer / in ports.

Common mistakes to avoid

  • Assuming a tool exists or guessing its name. Use tool_search to find the
    right tool (MCP servers add tools you can't know in advance), then read its
    exact ports from workflow_catalog.

  • Using an expression / template string in a binding. Use a data.* node.
  • Wiring json into a text port (or vice-versa) without a
    data.stringify / data.parse / data.extract between them.

  • A literal whose JSON type doesn't match the port (e.g. 5 into a text port).
  • Forgetting data.template.vars is a named object and
    logic.coalesce.values is an array.

  • Returning a document without running it through workflow_save.

More examples

Read references/examples.md (via skill_read, path references/examples.md)
for full worked documents: a linear pipeline, an if/coalesce branch, and a map
fan-out.