Workflowify — Technical Inspiration Map · Canvas 04-03 · 2026-06-03 (v.c)

Purpose and scope

This document maps each v1 pipeline stage to concrete sources of technical inspiration — papers, open-source tools, and existing patterns — and recommends a build strategy (reuse / adapt / build from scratch) for each.

Tag key: Reuse direct dependency · Adapt architectural pattern · Build from scratch · Risk hard open problem · Ref reference only; do not depend on. Each stage is also annotated LOCAL (runs in the workflowify MCP on the user's machine) or WEB (runs on the workflowify SaaS).

Architectural commitment (v.c): Workflowify v1 ships as a single MCP server installed into the host LLM (Claude Code, Claude Desktop, Hermes, OpenClaw, ChatGPT with MCP, Cursor, Windsurf, …). The host LLM extracts the conversation. The local MCP runs all value-handling work (parameterisation, fixture capture, PII redaction). Only an abstracted skeleton — no values, no prompts, no credentials — crosses to the SaaS for codegen. The local MCP then deploys, runs an equivalence test, and registers the scheduler. No file-format adapter layer is built — the host reads its own format natively.

Canonical pipeline — 20 stages, 5 phases

#StageWherePhaseData used
01MCP installationLOCALA. Setupnone (creates XDG dirs + _index.json)
02Workflow identity (codename + description)LOCALB. Invocationconversation summary (for description draft); RNG (codename)
03Skeleton sourcingLOCALB. Invocationfull conversation: values, prompts, tool args + results, control flow
04Skeleton extraction & parameterisationLOCALB. Invocationfull values from 03 → Presidio L1/L2 + LLM L3 to detect entities; populates param store and fixture store; emits value-free skeleton
05Pre-flight checkLOCALB. Invocationabstracted skeleton only — structure + slot names; regex scan confirms no leaked values
LOCAL → WEB transition: stripped skeleton sent over HTTPS. Zero values, zero prompts, zero credentials.
06Step classification (det vs non-det)WEBB. Invocationper-step tool kind + I/O shape
07Branch inferenceWEBB. Invocationcontrol-flow signals (errors, confirmations); slot refs appearing in conditions
08Edge case & failure-mode identificationWEBB. Invocationerror flags from trace; retry sequences; empty-result patterns; rate-limit signals
09Node body synthesisWEBB. Invocationtool kind + args schema (skeleton + external specs: MCP tools/list, OpenAPI, gh help); slot names
10Intermediate graph assemblyWEBB. Invocationnode specs + edges + state schema + trigger spec
11LangGraph codegenWEBB. Invocationintermediate graph JSON
12n8n codegenWEBB. Invocationintermediate graph JSON
WEB → LOCAL transition: generated artefact returned over HTTPS. Code / JSON with abstract get_param(...) calls; no embedded values.
13Provision runtimeLOCALC. Deploymentdeployment node specs (deps, MCP servers to register, env-var names); per-node setup-check scripts
14Correct source skillLOCALC. Deploymentoriginal skill.md content; codename + workflow invocation pattern
15Schedule registrationLOCALC. Deploymenttriggers[] spec; OS detection (launchd / cron / systemd / n8n / LangGraph cron)
16Deploy workflow fileLOCALC. Deploymentgenerated code/JSON; codename; updates _index.json
17Equivalence test (replay)LOCALC. Deploymentfixtures captured in stage 04 (recorded inputs/outputs); deployed workflow; param store
18Workflow execution (per run)LOCALD. Runtimeparams.json + env credentials + live tool I/O — no SaaS call at runtime
19Template libraryWEBE. Ongoingshared workflows' abstracted skeleton + description; no param-store contents
20Tool-update monitoring & re-gen offersWEBE. Ongoingregistered tool versions + schemas; notifies on schema diff

Privacy invariant. Stages 01–05 see actual values (locally). Stages 06–12 + 19–20 see only structural data. Stages 13–18 see actual values again (locally) for deployment, test, and runtime. The only data ever sent to the SaaS is the abstracted skeleton.

Research confidence: High (MCP spec, Presidio, LangGraph API, n8n JSON, ALLOY paper, current LLM pricing as of June 2026). Medium (host-LLM extraction reliability — depends on prompt discipline; step classification — no direct prior art; branch inference — no prior art; equivalence test for LLM nodes — open problem).

01MCP installation LOCAL

Problem: Make the workflowify MCP available to the host LLM with zero configuration UX of its own.

Framework selection

LibraryStarsProsConsVerdict
FastMCP (jlowin/fastmcp) ~10k+ Pythonic decorator API; Pydantic-native; matches LangGraph / Python backend; active development; comprehensive examples Single-maintainer; API still evolving in minor releases Recommended
MCP Python SDK (modelcontextprotocol/python-sdk) Official Anthropic-maintained; canonical reference; lowest abstraction More boilerplate; less ergonomic than FastMCP for tool-heavy servers Fallback

Reuse FastMCP. Confirm via /software-selection at build time. Inspirations: mcp-server-langgraph, Claude Code skills directory, skillify-skill UX.

02Workflow identity — codename + description LOCAL

Problem: Assign every workflow a stable, collision-free identifier before any state is written, plus a human-readable handle the host LLM can search by.

Each workflow gets two identifiers:

Tags are deliberately excluded — the description carries the keywords already, and a separate tag field drifts from descriptions over time.

_index.json entry shape

{
  "580D34AB": {
    "description":     "Weekly competitor pricing watch — Acme/Beta/Gamma, alerts under $20 baseline",
    "created":         "2026-06-03T16:54:00+02:00",
    "last_run":        "2026-06-08T09:00:00+02:00",
    "trigger":         "schedule:cron:0 9 * * MON",
    "deployment_path": "~/.local/share/workflowify/580D34AB.py",
    "test_status":     "pass"
  }
}

workflowify_find(query) uses substring + token match against descriptions — v1 default; zero new dependencies. V2 may promote to embedding-based ranking if real usage shows users searching with intent-words that miss literal tokens.

Build — small. Bulk of effort is the description-draft prompt template and the test-status field plumbing.

03Skeleton sourcing — host MCP, no file adapters LOCAL

Problem: Get the structural skeleton of the agent session into the workflowify pipeline, across every host the user might be working in.

The workflowify MCP exposes workflowify_emit_skeleton. The host LLM reads its own conversation history (which it already has in context) and produces the skeleton in-process. No CLI, no file adapters, no LiteLLM live-trace bridge for v1.

Why this beats a CLI + file-adapter layer

ArgumentDetail
Format coverage = MCP coverageEvery MCP-compatible host reads its own native format. One MCP server runs in all of them. No 11-format adapter table to maintain or evolve.
Semantic-aware extractionThe LLM that just ran the conversation has goal context, not just trace structure. A pure parser does not.
Privacy boundary unchangedPayload values stay inside the host process. The LLM that emits the skeleton already had access to them; no new trust boundary.
Sibling positioning to /skillify/skillify already runs as a host skill. Workflowify-as-MCP is the natural production sibling — same install model, same UX shape.
Distribution surface is minimalOne uv add; auto-discovered via MCP registries.

MCP tool surface (v1)

ToolPurpose
workflowify_emit_skeletonHost LLM extracts skeleton from current conversation. Generates codename (stage 02), drafts description, asks user to confirm, writes both to _index.json. Returns codename + deployed workflow code (after stages 04–17 complete).
workflowify_load_traceHost LLM loads a past session file (any format) from disk using its own file tools, then internally calls emit_skeleton.
workflowify_findSubstring + token match over _index.json descriptions; returns ranked codenames. Primary LLM lookup path.
workflowify_describeUpdate a workflow's description in _index.json. No filesystem churn.
workflowify_set_paramWrite a value to ~/.local/state/workflowify/<codename>/params.json.
workflowify_deployReceive generated code, write to disk, register schedule (stages 13–16).
workflowify_testRun the equivalence test (stage 17) on demand against captured fixtures.
workflowify_statusList workflows from _index.json: codename, description, trigger, last-run, test status.

Extraction contract — what the skeleton must contain

The MCP receives the full structure from the host LLM (values still present); stripping happens in stage 04, where Presidio needs actual values to detect entities. The contract here only governs completeness:

Hallucination mitigation

Reference inputs (not dependencies)

ReferenceHow it informs Workflowify
Model Context Protocol specDefines the tool / resource / prompt surface. Reuse
FastMCPPython MCP framework (stage 01). Reuse
OpenInference semantic conventions (Arize)Schema target — avoid reinventing field names. Adapt
LangChain chat_loadersConfirms that no off-the-shelf loader carries tool-call metadata. Validates the build-from-scratch path. Ref
Arize Phoenix · Langfuse · MLflow TracingConfirm no LiteLLM-equivalent for on-disk traces exists — all are runtime instrumentation. Validates MCP-first decision. Ref
ALLOY (arxiv 2510.10049)Context + Action Agent prompt pattern for low-hallucination extraction. Adapt

Build the MCP server. Reuse FastMCP + Pydantic + structured-output library.

04Skeleton extraction & parameterisation LOCAL

Problem: Process the host-LLM-emitted skeleton (with values still present) into three artefacts: (1) populated param store, (2) populated fixture store for stage 17, (3) value-free skeleton safe to send to SaaS. Stripping payload and identifying parameters are one operation.

Three-layer parameterisation stack

Architectural principle (LOCAL-first / deterministic-first): do cheap deterministic work before any LLM call. Three layers, applied in order:

LayerCatchesCostTool
1. Regex / “usual suspects” Conceptual list (not exhaustive): dates in all common formats; contact details (email, phone, URL); identifiers (UUID, file path, IP, IBAN, account number); monetary amounts and currencies; credentials (API keys, tokens, bearer prefixes — flagged and substituted with env-var refs). Near-zero, deterministic Presidio built-in recognizers + dateparser for date-format breadth
2. NER (local model) Named entities the regex layer cannot pattern-match: PERSON, ORG, GPE, PRODUCT, MONEY, QUANTITY, EVENT. Low (local spaCy / transformer) Microsoft Presidio (MIT; spaCy under the hood)
3. LLM residual + multi-trace diff Domain-specific entities the first two miss; cross-step coreference; LLM-prompt extraction (prompts the agent used in non-deterministic steps go into the param store as get_param("prompt_X") defaults). Strongest signal at v2: two traces with identical skeleton but different values at the same step → that field is a parameter. One LLM call per ambiguous slot ALLOY Identifier Agent pattern + Workflowify's own multi-trace differ (v2 for the latter)

Outputs of this stage

Why Presidio

Inspirations

SourceWhat we take from it
ALLOY Context + Action + Identifier AgentsAgent split: extract structure, then identify variable slots. Prompt pattern for layer 3.
/skillify / skillify-skillConversation-history scanning without LLM round-trips; intent reconstruction.
LangChain with_structured_output / PydanticSchema-enforced LLM output — the contract that makes layer 3 reliable.
Microsoft PresidioLayers 1 + 2 + PII redaction.

Build the three-layer pipeline + fixture writer. Reuse Presidio. Multi-trace differ is v2.

05Pre-flight check — safety net before SaaS send LOCAL

Problem: Validate the abstracted skeleton before it crosses to SaaS. Catches host-LLM hallucinations and prevents accidental value leakage.

CheckPass criterionFail action
Minimum step count≥ 3 meaningful stepsError: trace too short to generate a useful workflow
Schema validitySkeleton parses against the Pydantic TraceStep modelError: malformed skeleton — host LLM returned invalid structure
Tool call / result coherenceEvery tool-call step has a matching result step downstreamWarning: unpaired tool calls may indicate truncated trace
No raw valuesSkeleton contains only field names and type tags — no payload valuesBlock: stage 04 did not strip correctly
No credential patternsRegex scan for bearer tokens, API key prefixes (sk-, key_, Bearer), JWT, base64 secretsBlock: credentials still present despite redaction
Skeleton sizeCompressed skeleton ≤ configured token budgetError: trace too large; suggest filtering or splitting

Build Simple validation logic, ~50–100 lines. Fail loud on structural and privacy issues; warn on recoverable ones.

06Step classification — deterministic vs non-deterministic WEB

Problem: Label each trace step as deterministic (API call, pure function — same input → same output, cheap to replay) or non-deterministic (LLM reasoning — output varies, expensive).

Recommended pipeline: rules first, LLM for residual

ApproachCoverageCostNotes
Rule-based heuristics ~80–90% of steps. Signal: tool kind already declared in skeleton → deterministic; free-text LLM output with no tool call → non-deterministic Near-zero Apply first; skip LLM call for clear cases
LLM classifier (any sub-$0.50/1M-input model, routed via LiteLLM) Residual ambiguous cases (~10–20%): conditional checks, threshold-based decisions, steps mixing tool call + LLM interpretation Negligible at scale Few-shot, no CoT, single-token output. Cache-friendly: the few-shot prefix is identical across all calls.

Classifier prompt pattern (few-shot, no CoT)

Classify this agent trace step as DETERMINISTIC or NONDETERMINISTIC.
DETERMINISTIC = tool call, API call, pure function, DB query (same inputs → same outputs, no LLM needed).
NONDETERMINISTIC = LLM text generation, reasoning, summarisation, decision-making.

Examples:
{"tool": "search_web", "query": "weather Paris"} → DETERMINISTIC
{"thought": "I need to decide which product...", "output": "..."} → NONDETERMINISTIC
{"tool": "run_python", "code": "import json...", "stdout": "42"} → DETERMINISTIC

Classify: {step_json}
Answer (one word):

Academic inspiration

WorkContributionRelevance
AgWf + CrewAI (arxiv 2408.07720)Best taxonomy of node types: routers, ensembles, evaluators, improversAdapt Classification ontology
STRIDE (arxiv 2512.02228)“True Dynamism Score” rates each subtaskAdapt Extends binary to a score if needed
Blueprint First (arxiv 2508.02721)Validates decoupling deterministic logic from LLM sub-tasksRef
WorkflowLLM (arxiv 2411.05451)Fine-tuned Llama-3.1-8B on 106k workflowsRef v2+ route

Model choice — defaults and tiers (verified June 2026)

The classifier is model-agnostic. Route through LiteLLM so the model is a config string per deployment tier:

TierDefault modelCost ($/1M in/out)Rationale
SaaS — cost-optimisedDeepSeek V4 Flash + cache$0.14 / $0.28 (cached input $0.003)Cheapest credible option; few-shot prefix is cache-friendly. Per-classification cost effectively zero at scale.
SaaS — Western data residencyGemini 3.1 Flash or GPT-4o-mini$0.15–0.35 / $0.60–1.00~10× DeepSeek but sub-cent per generation.
Enterprise — air-gappedLlama 3 8B (self-hosted)Customer hardwareOpen weights.

Haiku 4.5 was repriced to $1 / $5 per 1M in late 2025 — no longer in the cheap tier. Verified via Anthropic docs and pricepertoken.com, June 2026.

Build Rules + LiteLLM-routed classifier. Re-evaluate model choice per /software-selection at v1 build time.

07Branch inference — extracting control flow WEB

Problem: Infer conditional edges from implicit signals in the trace — user confirmations, approval/rejection events, repeated sessions with different paths.

ALLOY gap — confirmed build-from-scratch

ALLOY explicitly does not support conditionals, loops, or error recovery (Section 7.1): “Alloy currently models workflows as single, linear examples that capture direct task executions without supporting richer procedural logic.” Branches must be added manually in the editor. This is a key differentiator for Workflowify — and a confirmed build task with no prior art to reuse.

Signal sources for branch detection

SignalWhere in skeletonInference
User confirmation / permission grantExplicit approval signal from the user after a tool call (host-agnostic — the host LLM identifies it semantically rather than via a field name)Step before approval = conditional gate; approved path = happy path; interrupted / rejected = alternative branch
Tool call followed by different next step across multiple tracesCompare two sessions with same skeleton but different post-tool-call sequencesFork point; condition = tool output value that differs between sessions
Threshold / score in tool output referenced in next LLM stepNumeric slot in tool result + LLM reasoning step referencing itCandidate for if score > threshold conditional edge
Error flag followed by retryError signal + subsequent same tool callError recovery branch — handed off to stage 08

Academic inspiration

WorkContributionRelevance
GraphMind (arxiv 2605.17617, Microsoft)Multi-trace merging → branching graphAdapt
A²Flow (AAAI 2025)Operator extraction + clustering + CoT abstraction across multiple demonstrationsAdapt
Classical process mining (van der Aalst)Petri nets with XOR gateways from event logsRef
SWIFT (arxiv 2604.25012)Workflow topologies transfer via few-shotRef v2+

Risk Hardest problem in the v1 pipeline. Single-trace inference is speculative; multi-trace comparison is more reliable but requires ≥2 sessions. V1 mitigation: human-in-the-loop validation before branch edges are committed.

08Edge case & failure-mode identification WEB

Problem: A workflow built from one happy-path trace will break in production on edge cases the trace never exhibited. Catalogue probable failure modes from signals in the trace (and from the tool kinds involved) so stage 09 can synthesise defensive bodies and the eventual v2 retry policy can populate intelligently.

Failure modes to identify

Failure modeSignal in skeletonAction in downstream stages
Transient error + retryTool error signal followed by same tool re-invocationStage 09 emits retry-with-backoff scaffold; v2 retry policy populates count/backoff
Empty / null resultTool result with empty array, null field, or zero rowsStage 09 emits empty-check guard; stage 07 considers an “empty → alternative branch” edge
Schema mismatchTool result shape differs from declared output schemaStage 09 emits a Pydantic ValidationError handler
Rate limit / 429HTTP 429 in result; explicit backoff in next stepStage 09 emits rate-limit-aware client config
Auth / credential expiry401 / 403; token refresh attempt; re-auth messageStage 13 (provision runtime) adds credential-validity setup check
TimeoutTool result with timeout indicator or unusually long elapsed timeStage 09 emits per-tool timeout config; default = 2× observed P95
Partial / paginated resultTool result with pagination cursor or “has_more” flagStage 09 emits pagination loop wrapper

Inspirations

SourceWhat we take from it
SRE error budget / failure-mode catalogues (Google SRE Book)Catalogue framework for common production failures.
Temporal.io activity-retry patternsSchema for retry policy (count, backoff, non-retryable error classes) — anticipates v2 retry-policy population.
OpenTelemetry GenAI semantic conventionsStandard error-classification field names (rate-limit, server, client, timeout).

Build Lightweight rule-based catalogue per failure mode; LLM consultation only for ambiguous schema-mismatch cases. Output: a failure_modes[] array attached to each node spec in stage 10's intermediate graph.

09Node body synthesis — making each step executable WEB

Problem: For each node, produce an executable body template with get_param() slots in place of every value. SaaS never sees the values — they live in the local param store.

What a body is

Node kindBody contents
Deterministic — HTTP API callEndpoint URL slot, HTTP method, headers (auth placeholder), body template with slots, expected response schema, status-code success criterion, retry-eligible error codes (from stage 08).
Deterministic — MCP tool callTarget MCP server name + tool name (lifted from skeleton), args schema (from server's tools/list), parameter slots, error handling. Body invokes via mcp_client.call(server, tool, args).
Deterministic — CLI invocationCommand + args template; required env vars; install/availability check; exit-code success criterion; stdout parsing rule.
Deterministic — Python library callImport + function invocation with slots; pinned version emitted into pyproject.toml; setup check = importlib.util.find_spec.
Deterministic — pure function / codeInline function body. V1: copy the code the agent executed verbatim and parameterise literals. V2: synthesise from input → output pairs.
Deterministic — DB querySQL template with placeholders, connection-config reference, expected row schema, empty-result handling.
Non-deterministic — LLM stepSystem + user prompt template referenced via get_param("prompt_X") (the actual prompt is in local param store, not in code); model selection (defaults to observed model); few-shot examples (multi-trace mode); output parsing — Pydantic schema if with_structured_output was used, free-text otherwise.

Schema sources for body synthesis

Body synthesis lifts schemas from authoritative sources, in this order: (1) the schema the skeleton itself carries; (2) the target tool's self-description — MCP tools/list, OpenAPI / OAS specs, gh help <cmd>, OpenAI / Anthropic function-call declarations; (3) the host LLM's knowledge of common libraries (asked at synthesis time for ambiguous Python library calls only). Synthesis fails loudly when none of these resolve.

Parameter store — runtime defaults

Synthesised bodies do not hard-code parameter values. They read from ~/.local/state/workflowify/<codename>/params.json via a get_param(name, default=...) helper. The helper resolves <codename> from a WORKFLOW_ID module-level constant. Defaults populated by stage 04. Users edit via workflowify_set_param — no regeneration required. Credentials never enter the store; they resolve via os.environ.

Setup checks

Each deterministic node emits a one-time setup block consumed by stage 13: availability check (which / importlib.util.find_spec / HEAD request); auth check (gh auth status, env var presence, token introspection); permissions check (where the service exposes it); smoke call (cheap read-only invocation of the same tool with safe args).

Inspirations

SourceWhat we take from it
ALLOY Action AgentPattern for reconstructing executed actions from a trace.
LangChain Tool / BaseToolEstablished contract shape: name, description, args_schema, invoke().
Anthropic Tool Use / OpenAI function-calling schemaInput/output contracts declared by tool-call protocols. Lift, don't re-derive.
n8n node parameter schemasParameter shape dictated by n8n's per-node-type schema for stage 12 export.
Programming-by-Example (FlashFill, Sketch)Ref V2+ — synthesising function bodies from I/O pairs.

Build the body-synthesis layer. V1: copy observed implementations verbatim, parameterise with stage 04 slots.

Risk: bodies that ran successfully once may fail in production because of environment drift. The setup-check block (stage 13) is the primary mitigation; the equivalence test (stage 17) is the second.

10Intermediate graph representation WEB

Problem: Neither ALLOY, LangGraph, nor n8n defines a standard JSON format for workflow graph topology. Workflowify needs a format-agnostic intermediate representation that all earlier stages contribute to and both code generators consume.

Recommended schema

{
  "workflow_id": "580D34AB",                  // 8-hex codename from stage 02
  "description": "...",                       // mirror of _index.json[workflow_id].description
  "state_schema": { "invoice_path": "str", "result": "str" },
  "nodes": [
    { "id": "validate_file", "type": "deterministic", "tool_name": "Bash",
      "body": { /* see stage 09 — CLI command, setup checks, parsing */ },
      "failure_modes": ["timeout", "exit_nonzero"],   // from stage 08
      "inputs": ["invoice_path"], "outputs": ["validated"] },
    { "id": "classify_invoice", "type": "llm",
      "body": { /* see stage 09 — system prompt slot, user template, model, output schema */ },
      "failure_modes": ["empty_result", "schema_mismatch"],
      "inputs": ["validated"], "outputs": ["category"] }
  ],
  "edges": [
    { "source": "__start__", "target": "validate_file" },
    { "source": "validate_file", "target": "classify_invoice" },
    { "source": "classify_invoice", "target": "__end__", "conditional": false }
  ],
  "conditional_edges": [
    { "source": "classify_invoice",
      "path_fn_stub": "def route(state): return 'approve' if state['score'] > 0.8 else 'review'",
      "targets": ["approve", "review"] }
  ],
  "triggers": [
    { "kind": "manual" }                            // schedule / webhook / queue in v2
  ],
  "retry_policy": null                              // v2; failure_modes populate it
  // param store path derives mechanically: ~/.local/state/workflowify/{workflow_id}/params.json
}

Prior art

ReferenceHow it's usedReuse potential
Langflow JSON (langflow2langgraph)Demonstrates the JSON-to-Python pipeline architecture.Adapt
NetworkX node-link JSON (Graphectory)Inspectable, well-documented Python-native format.Adapt
LangGraph get_graph()Validation / round-trip only.Ref
langgraph-codegen DSL3 stars — fails community dealbreaker.Ref DSL concept only

→ Define a Pydantic model for this schema. It is the contract between SaaS classifier/synthesiser and both code generators. Version from day one.

11LangGraph StateGraph code generation WEB

Problem: Emit valid, runnable Python code for a StateGraph from the intermediate graph JSON.

Key LangGraph API facts

Minimal StateGraph (2 deterministic + 1 LLM node)

from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from workflowify_runtime import get_param

WORKFLOW_ID = "580D34AB"

class State(TypedDict):
    text: str
    result: str

def preprocess(state: State) -> dict:          # deterministic
    return {"text": state["text"].strip()}

def llm_call(state: State) -> dict:            # non-deterministic
    prompt = get_param("prompt_summarise")
    response = model.invoke([SystemMessage(content=prompt), HumanMessage(content=state["text"])])
    return {"result": response.content}

def postprocess(state: State) -> dict:         # deterministic
    return {"result": state["result"].upper()}

builder = StateGraph(State)
builder.add_node("preprocess", preprocess)
builder.add_node("llm_call", llm_call)
builder.add_node("postprocess", postprocess)
builder.add_edge(START, "preprocess")
builder.add_edge("preprocess", "llm_call")
builder.add_edge("llm_call", "postprocess")
builder.add_edge("postprocess", END)
graph = builder.compile()

Code generation tools

ToolWhat it doesRelevance
Template + Black pattern (mcp-server-langgraph ADR-0047)Python string template with {node_id} / {node_body} slots; formatted with Black. Node body filled by stage 09. Round-trip: code → AST → graph → back to code.Build Primary approach. Full control over output.
langflow2langgraph (neuronaut73)Langflow JSON → LangGraph Python.Adapt Pipeline pattern
langgraph-codegen3 stars — fails community dealbreaker.Ref DSL concept only
LangFlow, LangGraph StudioLangFlow: no Python export. Studio: debugger.Ref Not reusable

Build per-node-type Python string templates with {node_id} / {node_body} slots formatted with black.format_str(). Validate with compiled.get_graph().draw_mermaid().

12n8n workflow JSON export WEB

Problem: Emit a valid n8n workflow JSON from the intermediate graph. Built in parallel with LangGraph generation; same intermediate graph, different serialiser.

n8n workflow JSON structure

FieldNotes
nodes[]Array of node objects: id, name, type, typeVersion, position, parameters. typeVersion must match n8n instance version — wrong value silently breaks import.
connectionsObject keyed by source node name. main: [[{node, type, index}]]. AI sub-nodes connect in reverse direction (sub-node → AI Agent).
active, settingsAlways false / {} in generated output.

Most used node types for Workflowify output

Use caseNode type string
HTTP / API calln8n-nodes-base.httpRequest
Code / pure functionn8n-nodes-base.code
LLM call@n8n/n8n-nodes-langchain.openAi
AI agent step@n8n/n8n-nodes-langchain.agent
Conditional branchn8n-nodes-base.if
Schedule triggern8n-nodes-base.scheduleTrigger

Python tools

ToolRelevance
nflow (gbarnev/nflow)11 stars — fails community dealbreaker. Ref node type registry only
n8n-workflow-builder (ferrants)Adapt Alternative if needed
n8n-schema-generator (yigitkonur)Reuse Validation layer (428 schemas, hourly-updated)
n8n AI workflow builder (n8n EE)Adapt Incremental mutation pattern

Build a thin custom n8n JSON serialiser (~100–150 lines). Wrap behind a WorkflowExporter interface. Validate against n8n-schema-generator schemas.

13Provision runtime — make the host ready to execute LOCAL

Problem: Generated code does not run on bare metal. Before the workflow can execute on the deployment host, the local MCP must install dependencies, register required MCP servers, verify credentials, and run the per-node setup checks emitted by stage 09.

Provisioning actions

ActionSourceFail action
Create venv at ~/.local/share/workflowify/<codename>/.venvper-workflow isolation; avoids contaminating user's global PythonError: cannot create venv (disk, permissions)
Install pinned deps via uv pip install -r requirements.txtStage 09 emits the requirements list (httpx, langgraph, etc., with pinned versions from observed trace)Error: dep install failed; surface diff to host LLM
Register required MCP servers in the deployment host's MCP configStage 09 emits the list of MCP servers the workflow calls (e.g. imap-mcp, postgres-mcp)Warning: MCP server not installed; suggest install command
Verify env-var presenceStage 09 emits the list of os.environ names the bodies readBlock: missing credential; prompt user to provide before continuing
Run per-node setup checksStage 09 emits availability / auth / permissions / smoke-call scriptsBlock on availability + auth failures; warn on permission gaps

Inspirations

Build the provision driver. Reuse uv as the dep installer.

14Correct source skill — rewrite the user's original skill LOCAL

Problem: If the user's workflowification was triggered from an existing skill (e.g. ~/.claude/skills/competitor-pricing/skill.md), that skill currently re-runs the ad-hoc agent flow every invocation — burning tokens. Replace it with a lean invocation of the deployed workflow.

Mechanics

If no source skill exists, this stage is a no-op (the workflow is just a standalone deployment).

Build Small. Inspirations: skillify-skill (skill detection patterns), conventional commits (backup naming pattern with timestamp suffix).

15Schedule registration — first-class output LOCAL

Problem: A workflow that can only be run manually is half-finished. The MCP must wire the chosen trigger to an OS-level scheduler so the workflow runs autonomously on cadence — and so the user never has to keep a Claude session open to trigger it.

Scheduler backends supported in v1

BackendOSMechanismOutput artefact
launchdmacOSGenerate a .plist file referencing the deployed workflow's invocation command; install to ~/Library/LaunchAgents/; launchctl load.workflowify.<codename>.plist
cronLinux / BSDAppend a line to the user's crontab; format per triggers[].cron.crontab entry tagged with ### WORKFLOWIFY:<codename>
systemd timerLinux (systemd)Generate a .timer + .service pair in ~/.config/systemd/user/; systemctl --user daemon-reload + enable --now.workflowify-<codename>.timer/.service
n8n schedulern8n backendThe generated n8n JSON (stage 12) already contains the scheduleTrigger node; import sets it active.n8n workflow with active schedule
LangGraph cronLangGraph backendRegister the workflow as a LangGraph cron job via the LangGraph platform / self-hosted scheduler.LangGraph cron registration

Selection logic

Build Small per-backend writer. Reuse standard library only (plistlib, subprocess); no scheduling framework dependency.

16Deploy workflow file LOCAL

Problem: Persist the generated code or JSON to a stable location and update the workflow registry.

Build Trivial. ~20 lines.

17Equivalence test — replay against recorded fixtures LOCAL

Problem: Confirm that the generated workflow produces the same output as the original conversation when run on the same data. Without this, the user has no evidence the workflowification preserved behaviour.

How it works

Comparison rules — handling LLM non-determinism

Node kindComparison methodPass criterion
DeterministicExact match on output valueEqual modulo whitespace + insignificant numeric precision
Non-deterministic (LLM)v1: schema match + non-empty + substring overlap with recorded output; v2: embedding similarity ≥ thresholdOutput conforms to declared Pydantic schema AND meaningful overlap with original
Whole workflowFinal state matches recorded final state, field-by-field with each field using its own ruleAll fields pass; produces a per-field diff on failure

Re-test triggers

Inspirations

Risk LLM-node equivalence is a genuinely hard problem. V1 settles for “schema-valid + meaningful overlap”; embeddings deferred to v2. Bigger risk: a non-deterministic node that always returns slightly different output triggers false failures. Mitigation: explicit accept-diff path + HITL review.

Build Test runner + diff renderer. Reuse Pydantic for schema validation, difflib for textual diff.

18Workflow execution — per run LOCAL

Problem: The deployed workflow runs on cadence (or on-demand) entirely on the user's machine. No workflowify SaaS call at runtime. This is the load-bearing claim of the whole product.

Build The runtime helper library (workflowify_runtimeget_param, log_run, etc.) — ~100 lines. Reuse LangGraph / n8n's own execution engines.

19Template library WEB

Problem: Users with similar needs should be able to clone someone else's workflow rather than building from scratch. This drives community adoption (canvas distribution strategy) and creates a network effect over time (canvas unfair advantage).

Revenue model implication: template authors can opt into revenue-share if a template gates Pro / Enterprise features (canvas Phase 3 marketplace).

Build SaaS endpoints + MCP tools for share/search/clone. Detailed design deferred until stage 18 is shipping and there's volume to justify.

20Tool-update monitoring & re-generation offers WEB

Problem: Workflows reference external tools (LangGraph, n8n, MCP servers, CLIs, APIs). When a referenced tool changes — new node version, schema migration, deprecation — the deployed workflow may break silently or stop benefiting from improvements. Surface diffs proactively.

Build SaaS-side watcher + diff classifier + push protocol. The classifier is the hard part; rules-driven for v1, LLM-assisted for ambiguous diffs.

Other generalisation axes — beyond entity parameterisation

Entity parameterisation (stage 04) is one of eight axes on which a single observed trace must generalise to become a reusable workflow. The remaining seven, and their v1 / v2 scoping:

#AxisWhat it generalisesV1?Approach
1Entity / parameter extractionDates, names, IDs, monetary amounts, credentials, … (stage 04)Yes3-layer stack (stage 04)
2Conditional thresholdsif score > 0.8 — numeric thresholds (stage 07)YesHITL validation (stage 07)
3Loop / iteration detectionN tool calls with similar args → for item in list. Single-trace heuristic feasible.Yes (basic)Rule: ≥3 consecutive same-tool calls with structurally similar args → loop candidate; HITL confirm (stage 09)
4Sub-workflow abstractionRecurring sequence across traces → named sub-graph.V2ALLOY Action Agent grouping + multi-trace clustering
5State schema typingTool output: string, number, structured JSON, or free-form blob?YesPydantic schema inference; default to str with HITL upgrade prompt (stage 10)
6Trigger generalisationUser typed → schedule / webhook / queue / event.Yes (manual + schedule)triggers[] slot in intermediate graph (stage 10); v1 emits manual + schedule; v2 adds webhook + queue
7Error / retry policySingle happy path → retry count, backoff, dead-letter.V2 (slot present in v1)retry_policy slot populated in v2 from stage 08's failure_modes output
8Credentials / auth substitutionFounder's token → per-tenant env-var reference.YesPresidio flags credential strings (stage 04 layer 1); stage 09 substitutes with os.environ reads

Cross-cutting generalisation — what stage 04 + the other axes cannot do alone

The stages above cover per-trace generalisation. Three problems remain that need cross-trace or cross-customer infrastructure:

ApproachSourceRole
Multi-trace mergingGraphMind (arxiv 2605.17617) · A²Flow (AAAI 2025)V2 — strongest signal for axis 1 (which fields are actually variable), axis 4 (recurring sub-sequences), and axis 7 (retry-worthy error patterns). Requires ≥2 traces of the same skeleton.
Fine-tuned classifierWorkflowLLM (arxiv 2411.05451) — Llama-3.1-8B on 106k workflowsV3+ — trained on Workflowify's own corpus. Replaces prompt-driven layer 3 (stage 04) and the LLM classifier (stage 06).
Human-in-the-loop validationCanvas v1 designV1 — explicit review of parameter extraction, branch inference, loop detection, and equivalence test before workflow is marked ready. One review step per generation.

Risk V1 generalisation is single-trace, prompt-driven, and incomplete by design. HITL + equivalence test are the safety nets. V2 unlocks bigger wins through multi-trace merging.

Summary — inspiration map by stage

#StageStrategyPrimary reference(s)Risk
01MCP installationReuse FastMCPFastMCP · MCP Python SDK · mcp-server-langgraphLow
02Workflow identityBuildDescription-draft prompt; opaque-codename precedent (Git SHAs, Linear IDs)Low
03Skeleton sourcingBuild MCP toolMCP spec · OpenInference schema · ALLOY Context/Action prompt pattern · LangChain with_structured_outputLow–medium (hallucination — mitigated by stage 05)
04Skeleton extraction & parameterisationBuild 3-layer; Reuse PresidioPresidio (MIT) for L1+L2+PII · ALLOY Identifier for L3 · LangChain Pydantic · skillify patternsLow for L1–L2; medium for L3
05Pre-flight checkBuildPydantic schema validation · credential regex patternsLow — high-value as safety net
06Step classificationBuild (rules + LiteLLM-routed cheap model)AgWf+CrewAI taxonomy · STRIDE · Blueprint First · DeepSeek V4 Flash as defaultLow–medium
07Branch inferenceBuildGraphMind · A²Flow · van der Aalst process miningHigh No prior art
08Edge case & failure-mode identificationBuildSRE failure-mode catalogues · Temporal retry patterns · OTel GenAI error conventionsLow (rules) — feeds v2 retry policy
09Node body synthesisBuildALLOY Action Agent · LangChain Tool · Anthropic/OpenAI tool-use schemas · n8n-schema-generator · PbE (v2+)Low–medium
10Intermediate graphBuild schemaLangflow JSON + langflow2langgraph · NetworkX node-link JSONLow — design task
11LangGraph codegenBuild (template + Black)mcp-server-langgraph ADR-0047 · langflow2langgraphLow
12n8n exportBuild (custom serialiser)n8n-schema-generator · n8n AI builder incremental patternLow
13Provision runtimeBuild; Reuse uvuv (Astral) · direnv · Ansible idempotence patternsLow–medium (environment drift)
14Correct source skillBuildskillify-skill detection patterns · conventional commits backup namingLow
15Schedule registrationBuildplistlib · crontab · systemd timer / .service templates · n8n / LangGraph cron APIsLow
16Deploy workflow fileBuildAtomic write (.tmp + rename) idiomLow
17Equivalence test (replay)BuildPytest fixture pattern · approval testing (Falco) · VCR.pyMedium-high LLM-node equivalence is hard; v1 settles for schema + overlap
18Workflow executionBuild runtime helper; Reuse LangGraph / n8n enginesLangGraph runtime · n8n runtimeLow
19Template libraryBuild (deferred)Description-only matching (consistent with stage 02)Low (design deferred)
20Tool-update monitoringBuildSemVer / typeVersion diff patterns · OpenAPI diff toolsMedium (diff classification)
Other generalisation axesBuild v1 axes; defer v2 axesLoop heuristics · Pydantic inference · credential substitutionLow–medium per axis
Cross-cutting generalisationBuild v1 (HITL); Adapt v2HITL validation (v1) · A²Flow + GraphMind multi-trace (v2) · WorkflowLLM fine-tuning (v3)High Prompt-driven v1 is incomplete by design

Worked example — competitor pricing watch

You are in Claude Code, Wednesday afternoon. You type:

“Fetch the pricing pages from Acme Corp (acme.com/pricing), Beta Inc (beta.io/pricing) and Gamma SA (gamma.fr/tarifs). Extract their starter-plan prices. Compare to my $19.90 — if any are below $20, email julius@renk.org a summary signed 'Pricing Watch'.”

Claude executes: 3 × WebFetch → LLM extraction → comparison → asks confirmation → you say yes → invokes imap_send_email on your email-MCP to send the summary. Conversation ends. You then type @workflowify generate me a weekly job from this.

#StageThis example
01MCP installation LOCALAlready installed last month (uv add workflowify-mcp). ~/.local/state/workflowify/_index.json exists with 3 prior workflows. No-op for this run.
02Workflow identity LOCALMCP generates codename 580D34AB. Claude drafts description: “Weekly competitor pricing watch — Acme/Beta/Gamma, alerts under $20 baseline.” You accept silently. Written to _index.json.
03Skeleton sourcing LOCALClaude returns the 7-step skeleton (with values still present, per the extraction contract) to the local MCP. Steps: fetch_acme, fetch_beta, fetch_gamma, extract_prices, compare_threshold, confirm_send, send_email.
04Skeleton extraction & parameterisation LOCAL3-layer pass on full values. Param store populated at ~/.local/state/workflowify/580D34AB/params.json with URLs, vendor names, baseline $19.90, signature “Pricing Watch”, the LLM extraction prompt, IMAP account “j@r”, recipient. SMTP password flagged as credential — substituted with os.environ["SMTP_PASS"], never stored. Fixture written to fixtures/seed.json with input state + observed prices + final email sent confirmation. Skeleton stripped of values, ready to send.
05Pre-flight check LOCAL7 steps ≥ 3 ✓; Pydantic shape ✓; no raw values ✓; no bare credentials ✓; ~3 KB ≤ token budget ✓.
LOCAL → WEB: stripped skeleton sent over HTTPS.
06Step classification WEBfetch_*deterministic — HTTP API. extract_pricesnon-deterministic — LLM. compare_thresholddeterministic — pure Python. send_emaildeterministic — MCP tool call (skeleton declared kind = MCP, server = imap-mcp). confirm_send → branch gate, not a body node.
07Branch inference WEB(a) if user_approved: send else: skip; (b) if any(p < threshold for p in prices): send_alert else: end.
08Edge cases WEBFor fetch_*: rate-limit guard, timeout, empty-response. For extract_prices: empty-result handler (vendor's HTML changed); schema-mismatch handler. For send_email: auth-expiry detector. failure_modes[] attached to each node.
09Node body synthesis WEBfetch_*: httpx.get(get_param("acme_url")) + HEAD reachability check. extract_prices: prompt via get_param("prompt_extract"), model = Sonnet (observed), Pydantic schema List[VendorPrice]. compare_threshold: 3-line Python. send_email: mcp_client.call("imap_send_email", account=get_param("imap_account"), to=get_param("recipient"), ...) + setup checks.
10Intermediate graph WEBPydantic JSON: workflow_id = "580D34AB"; state schema; nodes; conditional edges; triggers = [{kind: "schedule", cron: "0 9 * * MON"}]; retry_policy = null.
11LangGraph codegen WEBTemplate + Black emits Python code with WORKFLOW_ID = "580D34AB" module constant. draw_mermaid() confirms topology.
12n8n codegen WEBSame intermediate graph → JSON with 3×httpRequest, 1×openAi, 1×code, 1×if, 1×emailSend, 1×scheduleTrigger.
WEB → LOCAL: generated artefact returned over HTTPS.
13Provision runtime LOCALCreates .venv at ~/.local/share/workflowify/580D34AB/.venv; uv pip install httpx langgraph anthropic .... Verifies imap-mcp registered in host's MCP config (it is). Verifies SMTP_PASS in env (it is). Runs setup checks: httpx.head() against each URL OK; imap_send_email smoke call with safe args OK.
14Correct source skill LOCALNo source skill detected (you invoked via @workflowify on an ad-hoc conversation). No-op.
15Schedule registration LOCALmacOS detected. Generates workflowify.580D34AB.plist with cron expression 0 9 * * MON; installs to ~/Library/LaunchAgents/; launchctl load.
16Deploy workflow file LOCALWrites ~/.local/share/workflowify/580D34AB.py (atomic via .tmp + rename). Updates _index.json with deployment_path, trigger.
17Equivalence test LOCALReplays the workflow against fixtures/seed.json. fetch_* outputs match recorded HTML schemas. extract_prices output: Pydantic schema match + substring overlap with recorded vendors and prices (Beta $18 still detected). compare_threshold returns True (matches recorded). send_email mocked — verifies the call would have been made with the recorded recipient. Test passes; _index.json[580D34AB].test_status = "pass". Workflow marked ready.
18Workflow execution LOCALMonday 09:00, launchd fires. Workflow runs locally — reads params.json, calls Acme/Beta/Gamma directly, invokes Anthropic Sonnet on metered credit, sends email via imap-MCP. last_run updated. No workflowify SaaS call.
19Template library WEBOptional: workflowify_share 580D34AB uploads the template (skeleton + code, no params). A colleague can later clone it and fill in their own vendors.
20Tool-update monitoring WEBThree months later, imap-mcp v0.7 adds an optional html_body parameter. SaaS notifies on next workflowify_status. You accept the re-gen offer; stages 06–17 re-run silently against the existing skeleton; equivalence test confirms behaviour preserved.

Parameter-detection detail (stage 04 sub-table)

StepParameterDetection layerDefault (in param store)
fetch_acmeurlL1 regex (URL)https://acme.com/pricing
fetch_betaurlL1 regex (URL)https://beta.io/pricing
fetch_gammaurlL1 regex (URL)https://gamma.fr/tarifs
(all fetch_*)vendor_nameL2 NER (ORG)“Acme Corp”, “Beta Inc”, “Gamma SA”
extract_pricesprompt_extractL3 LLM residual(full extraction prompt extracted from trace — stored locally, never to SaaS)
compare_thresholdbaselineL3 LLM residual19.90
compare_thresholdthresholdL3 LLM residual20.00 (derived from baseline)
send_emailrecipientL1 regex (EMAIL)julius@renk.org
send_emailsignatureL2 NER (ORG)“Pricing Watch”
send_emailimap_accountlifted from MCP tool argsj@r
send_emailsmtp_passwordL1 credential patternnot stored — resolves via os.environ["SMTP_PASS"]

Outcome: ~/.local/share/workflowify/580D34AB.py + launchd plist running every Monday at 09:00; test status “pass”. Run-time LLM calls go directly to Anthropic on metered credit, not your Claude subscription. Months later, you say “raise my pricing alert threshold to $25”; host LLM calls workflowify_find("pricing alert")580D34ABworkflowify_set_param 580D34AB threshold 25. No file rewrite, no codename you had to remember.