Plugin node — `models.PluginNodeType`
The external adapter primitive, and the most powerful node type. It hands execution off to a live external process you own — running anywhere, holding its own connections and state — that the runtime
The external adapter primitive, and the most powerful node type. It hands execution off to a live external process you own — running anywhere, holding its own connections and state — that the runtime (Fractal) calls into over NATS. It is the one primitive that does not compile down to the others: a plugin is a real process, not compiled logic, which is exactly why it can do things the compiled primitives can't.
The full authoring side of a plugin — how you write the process — lives in the separate
inflow-plugin-sdk(sdkv1) repository. This page covers the node as it exists ininflow-fusion(models.PluginRule) and how it fits the node taxonomy. For building a plugin, see that SDK's README and itsdocs/architecture.md,docs/jobs-and-commands.md,docs/protocol-inflowv1.md,cookbook.md, and theskills/inflow-plugin/SKILL.mdagent skill.
Rule data (the node, in this SDK)
type PluginRule struct {
InfraIsolated InfraIsolated // account/seed/cred/url for this plugin's isolated NATS identity
Request string
SubjectPrefix string // defaults to "inflow.cpu.{uniqId}"
CancelAfterIdle int8 // minutes; default 15
Body map[string]any
}
plugin, err := nodes.NewPluginNode("jira",
nodes.WithIdleWaitMinutes(30),
)
If you don't supply WithPluginIsolated, NewPluginNode fetches (and caches)
credentials for the builtin plugins account via
spaces.GetCredOnBuiltinPluginAcc — this requires inflow.InitBackend to have run.
For per-instance credential scoping (so one plugin instance's client can't see
another's traffic), see spaces.PluginCredentialStrictPermission and
spaces.CreateUserCredential.
Why it doesn't compile to primitives
A compiled primitive is inert logic the engine runs. A plugin is a persistent process, so it can:
- hold long-running state — open DB/queue/socket connections and keep them warm;
- originate events — run background loops that surface external happenings as node activity;
- be an adapter — translate any external protocol into Inflowenger context;
- carry its own UI — every action ships a JSON-Schema/UI-Schema form (rendered
by JSON Forms with
x-inflow-uirenderers) so users configure the node visually; - read and write flow context mid-execution, report progress, route the flow (below), and even stop the flow from inside its handler.
Because of this, a single plugin node is enough to build an entire workflow- automation system on top of Inflowenger — it is the platform's open-ended extension point. Everything else is a closed set of compiled primitives; the plugin is the escape hatch to the outside world.
Two registers of interaction (subject naming)
The runtime talks to a plugin in two ways, and the subject tells you which:
- UI / arguments (
@-prefixed oninflow.v1.<PLUGIN_ID>.*) — describe/ configure me:@intro,@settings,@actions,@form. Pure metadata; nothing executes. - Execution (
inflow.cpu.<PLUGIN_ID>.*) — run me: the node's main call, issued by the Fractal at runtime. It starts a Job — the plugin acks with ajobId, then works asynchronously, streamingprogress, reading/writing context (context/current,context/path,commit), and finallydone, all keyed by thatjobId.
The SubjectPrefix field above (default inflow.cpu.{uniqId}) is the execution-plane
prefix for this node instance.
A plugin can route the flow (next_tags)
Among the job commands is one that decides where execution goes next. The plugin sends the tags to keep; the engine deactivates every outgoing edge of the node and re-activates only those tagged with one of them — exactly what a Contract rule does, decided by an external process instead:
p.AddAction(sdkv1.Action{Method: "classify", RequestHandler: func(job sdkv1.Job) {
job.CmdNextFilter([]string{"escalate"}) // subject <job>/next_tags, payload "escalate"
job.Done(map[string]any{"reason": "amount over limit"})
}})
This is what lets a model node route the diagram. An LLM plugin exposes the
node's bound functions/tools as tagged outputs; when the model answers with a tool
call, the plugin turns that choice into CmdNextFilter([...]) and only that port
fires. The model selects among ports the designer drew — it cannot invent one, and
everything downstream of each port is already defined. Same for an MCP node, or any
plugin that picks its own continuation.
The tags that did the selecting are recorded and surface in the run's
edge.select event (../logs.md). Full mechanism and semantics:
../routing.md; the wire form is next_tags in the SDK's
docs/protocol-inflowv1.md and docs/jobs-and-commands.md.
Frontend representation (inspector)
Palette type pluginNative (PluginNativeNode.vue + PluginNativeDrawer.vue).
Relevant node.data:
| Field | Backend | Meaning |
|---|---|---|
subject_prefix | SubjectPrefix | execution-plane subject prefix |
request | Request | which action/method to invoke |
idle_min | CancelAfterIdle | cancel the call after N idle minutes |
body{} | Body | payload passed to the action |
infra_isolated.account | InfraIsolated | account/space to run the plugin's identity under |
A packaged plugin can also appear on the canvas as an extension instance —
palette type my_a_ext (extension_raw + settings), whose form was declared by
the extension itself and filled in via JSON Forms; it compiles to the plugin (or
extrinsic) it wraps. See from-frontend.md.
Compiles to
The compiler hook reads the fields above and builds nodes.NewPluginNode(request, …)
→ models.PluginRule, setting node.Type = models.PluginNodeType.
Next
- ../routing.md — tag routing across all three deciders
- ../plugin-svc-calls.md — serving the
request/svc.<ACTION>calls a running plugin job makes back into your backend - extrinsic.md — the thin internal-call counterpart; see the comparison table there
- ../architecture.md — Context · Workflows · Fractals · Adapters, and where plugins sit
- inflow-plugin-sdk (separate repo) — how to actually write a plugin process
- from-frontend.md · ../nodes.md
inflow-fusion/docs/nodes/plugin.md in the inflow-fusion repo.