Example imports¶
Four n8n workflows and one Dify app to import into Studio, and — more usefully —
what each one actually produces. Every figure below was measured by running
the file through the importer, not estimated. The Dify example needs 0.9.0 or
later: 0.8.0 and earlier answer a Dify import with 501 not_implemented.
Import one from the SPA, or over the API:
curl -X POST http://127.0.0.1:8091/api/projects/default/import/n8n \
-H 'Content-Type: application/json' \
-d "$(jq -n --rawfile raw examples/n8n/chat-agent.json '{raw: $raw}')"
On a project that is already bound to a platform environment, that answers
422 alias_required. A bound project has an approved generation, so an
import has to say which alias it belongs to — add alias beside raw:
The alias must name one of the project's environments — default is the one the
zero-config binding creates; any other name answers 422 unknown_alias. The
command above is written for a fresh instance, where default is unbound.
The two are exclusive in the other direction too: an alias on an unbound
project is 409 project_unbound, because there is no approved generation for
it to name.
| File | Blocking gaps | Manual gaps | Info notes | Converts to |
|---|---|---|---|---|
n8n/chat-agent.json |
none | entry_mechanism_not_carried (the trigger) |
the iteration bound | a one-agent spec |
n8n/hello-agent.json |
1 (source_cannot_run) |
entry_mechanism_not_carried (the trigger) |
the iteration bound | a one-agent spec n8n itself never ran |
n8n/agent-with-tool.json |
2 (source_cannot_run, tool_without_backing) |
entry_mechanism_not_carried (the trigger) |
the iteration bound | a one-agent spec, with the tool left out and carded |
n8n/needs-work.json |
3 | entry_mechanism_not_carried (the trigger) |
— | nothing usable — every step is unrepresentable |
The one thing to know first¶
Every n8n workflow's trigger becomes a card, and that is normal. Kneo has no
trigger concept, so the card says "Trigger must be recreated outside Kneo". For
a webhook, chat, schedule or event trigger it is manual, because the route or
schedule is real behaviour to recreate; for a manual trigger it is info. A
first import showing that one manual gap, and otherwise only info notes, is the
clean outcome. An
info note asks nothing of you: here it says the agent's tool loop runs with
n8n's default iteration bound, which the spec leaves to the platform's default.
chat-agent.json exists so you have something to compare against, because "one
gap" is otherwise indistinguishable from "something went wrong".
Why hello-agent.json blocks¶
It is a workflow n8n itself refuses to run, and Studio says so rather than
handing you a working agent the source never was. A schedule starts it, and the
agent's prompt is auto, which reads the incoming item's chatInput, but a
schedule's item has no chatInput. On top of that, an OpenAI model under n8n's
tools agent needs at least one tool, and it has none. The pinned n8n fails that
agent on every item, so the card is source_cannot_run, and the fix belongs in
the source. chat-agent.json is the same agent made runnable. A chat trigger
supplies chatInput, and an Anthropic model needs no tool. agent-with-tool.json
has the first defect too, because a webhook's item carries the request under
body, not chatInput.
What converts¶
Measured against the pinned release (n8n@1.62.1 — see N8N_COMPAT):
Converts. Agent nodes and the language model attached to one, where n8n
itself can run the agent: an agent n8n fails on every item — no model, a model
its executor refuses, or an auto prompt with no chatInput to read — is a
blocking source_cannot_run card instead. Several agents
in sequence become a sequential workflow — each agent after the first needs a
defined prompt, since an auto prompt after an agent has no chatInput to
read. An if between agents becomes a graph workflow, so the branching shape
survives.
Tools do not convert, and this is the limitation to know about.
A Kneo tool is backed by exactly one of a Python implementation import path,
a declared MCP server, or another agent — and an n8n export describes none
of them. A toolHttpRequest names an ordinary REST endpoint, which is not an
MCP server: pointing Kneo at it would send MCP protocol traffic to your API.
A toolCode is JavaScript, where an implementation is a Python import path.
So every ai_tool attachment that is a tool node (toolHttpRequest,
toolCode) becomes a blocking card naming the tool, and the tool is left
out of the spec. Studio previously emitted it with an empty backing, which the
platform refused outright while the import reported success — a draft that could
not be governed and nothing saying why.
To carry a tool across, add it to the spec yourself with a real backing after
the import. An n8n MCP client node is the shape Kneo could express
directly, but it is not among the node types Studio converts at the pinned
n8n@1.62.1, so it imports as a blocking proprietary_node card today.
The other ai_tool case is blocking too: n8n's agent as tool pattern,
where a sub-agent is attached to a main agent to delegate to. Kneo has no form
for it. The sub-agent imports as a separate step with nothing wired to it, and
draws three blocking cards — "Offered as a tool, imported as a step", "The
source's trigger never reaches this node", and a second-entry-point card. The
source let the main agent decide whether to call it; decide whether the
delegation mattered, then wire it in or remove it.
Does not convert. Ordinary processing steps — httpRequest, code, and
anything outside the pinned node list — each become a blocking
proprietary_node gap: "No Kneo equivalent". This is the part worth
internalising before importing something real:
A Kneo spec is agent-centric. An n8n workflow that is mostly plain steps — fetch, transform, branch, write — has almost nothing to map onto, and will import as a wall of blocking gaps. That is the importer being honest, not broken. The workflows that convert well are the ones where an agent is doing the work and the other nodes are feeding it.
Two partial cases, both blocking:
- an
ifcondition is compiled by n8n to an expression Kneo cannot carry, so the branch shape converts and the condition becomes anexpressiongap; - a credential written into a node —
needs-work.jsonputs one in anAuthorizationheader — becomes aninline_secretgap. The value is not carried into the spec; bind a credential reference instead.
Why needs-work.json is here¶
A realistic first import often looks like this one, and seeing three blocking
gaps with no reference point reads as failure. It is the honest answer for a
workflow with no agent in it: Fetch records and Vendor widget have no Kneo
equivalent, and the header carries a secret.
Import it after chat-agent.json, so the difference is visible rather than
alarming. Its blocking gaps are what stops it exporting — that gate is the
product working.
The API key in it is a placeholder (sk-live-NOT-A-REAL-KEY) and matches
nothing.
A Dify app: dify/answer-workflow.yml¶
A workflow app in the shape Dify's Export DSL writes (DSL 0.7.0): a start
form asking for a question, one LLM answering it, and an end node returning the
answer. Import it with Source: Dify, or over the API at /import/dify, with
the same body and the same alias rules as above:
curl -X POST http://127.0.0.1:8091/api/projects/default/import/dify \
-H 'Content-Type: application/json' \
-d "$(jq -n --rawfile raw examples/dify/answer-workflow.yml '{raw: $raw}')"
| File | Blocking gaps | Manual gaps | Info notes | Converts to |
|---|---|---|---|---|
dify/answer-workflow.yml |
none | run_input ×2 (the start form, the LLM's user message), output_formatting_not_carried (the end node's named output) |
the start | a one-agent spec: the model and the system prompt |
On Dify the start's card is info, not manual: a start is how a caller
begins a run, which a Kneo run already is. What a clean Dify import asks of you
instead is run input — what each caller now supplies — and the response format.
This one is clean because an LLM does all the work. Most real Dify apps are not:
over 614 real exports, 607 convert and 43 of those carry no blocking card. What
blocks the rest is in Importing a Dify app.