@addai/node-flows
v1.0.0
Published
The +Ai Flows execution engine, with no host attached. Runs the same graph on Cloud Run and on a paired +Ai node.
Readme
@addai/node-flows
The +Ai Flows execution engine, with no host attached.
This package walks a flow graph and runs the node handlers that can run
anywhere. It holds no Supabase client, no service-role key, no Express app and
no opinion about where run state lives. Everything it cannot do by itself
arrives through context and hooks.
It exists because there used to be three copies of this code — the Cloud Run
backend, the execute-flow edge function, and a self-hosted runner — and they
disagreed with each other. A flow's behaviour must not depend on which machine
picked it up.
Hosts
| Host | Handlers | State | Key material | |---|---|---|---| | Cloud Run | all of them | service-role client | full | | +Ai node | local tier + proxies | daemon-token RPCs | none |
The node deliberately has no key material. It runs on somebody's laptop, and a laptop holding the service-role key holds every workspace in the product.
Using it
import { executeWorkflowGraph, createHandlerRegistry } from '@addai/node-flows';
const handlers = createHandlerRegistry({
privileged: { customNode, review, page, /* ... */ }, // Cloud Run
// or:
proxyFactory: (type) => makeProxyHandler(type), // a +Ai node
});
const result = await executeWorkflowGraph({
nodes, edges, inputData,
endConditions,
hooks: { onNodeStarted, onNodeCompleted, step },
context: { handlers, workspaceId, flowRunId, flowId },
});context.handlers is not optional. The engine used to import its registry
directly, which is what made it impossible to run anywhere the full handler set
could not be imported. Omitting it now fails immediately with a message saying
so, rather than reporting every node in the flow as an unknown type.
The two tiers
A handler is local when it can do its whole job with the node's data, the
run's outputs, and the network. It is privileged when it needs a
service-role client or the credential encryption key. The split follows the
import graph rather than anyone's judgement, and lives in src/handlers.js as
LOCAL_HANDLERS and PRIVILEGED_NODE_TYPES.
Add a node type that reaches for the database, add it to
PRIVILEGED_NODE_TYPES, and both hosts are correct. A type on neither list
fails loudly as "Unknown node type" — the right outcome for a type nobody has
decided about yet.
httpRequest and code being local is the entire point of running flows on a
node: on that machine they reach its LAN, its VPN and its filesystem, which no
amount of cloud capacity substitutes for.
Sandboxing
isolated-vm is an optional dependency. It is a native build, and making
it mandatory would mean a laptop that cannot compile it cannot run flows at
all. When it is absent the sandbox falls back to node's built-in vm, which is
a real boundary but a weaker one.
Call sandboxAvailability() to find out which one you got. The +Ai node reports
it in its capabilities and the Studio states it plainly, because a UI that
implies the stronger sandbox when the weaker one is running is worse than one
that says nothing.
Tests
npm test