noxus-sdk
that can contribute nodes, integrations with credentials, polling triggers,
and knowledge-base data sources — all versioned and deployed together.
At install time the platform reads the plugin’s manifest (reflected from your code),
provisions an isolated sandbox, and starts a long-lived worker in it. At run time the
platform dispatches component calls to that worker over JSON-RPC. You never register
anything globally: the manifest is the contract.
What a plugin can provide
A plugin must declare at least one node, integration, trigger, or data source. Each component surfaces in the matching place in the product — nodes in the flow editor’s palette, integrations in workspace settings, triggers in the trigger catalogue, data sources in the knowledge-base source picker.Nodes — V2 and V1
Nodes are the building blocks of workflows. Plugins provide two kinds, and a single plugin can mix them freely:V2 nodes (preferred)
Connector-free, mirroring the platform’s native V2 nodes. A node is two schemas:
a config schema whose fields are its settings (a field marked
bindable=True
becomes an input that takes a literal or a :var[...] reference), and an output
schema that declares every output. Everything arrives on self.config.V1 nodes (legacy)
Edge-connector nodes for V1 flows. Inputs and outputs are
Connector objects wired
by edges; inputs arrive as keyword arguments to call. Use only when you specifically
need a V1 flow.BasePlugin.get_manifest() splits the
classes returned from nodes() into two manifest lists (nodes and nodes_v2) by
issubclass(n, BaseNodeV2). On the platform each kind is backed by its own generic
executor, so there is no per-node synthesized class — the executor reads the component’s
shape from the stored manifest and dispatches by (plugin, node_name).
Integrations and credentials
An integration manages authentication and credentials for an external service. You declare aBaseCredentials schema (its fields render in workspace settings) and a
BaseIntegration; a node lists which credential types it needs and reads them at
execution time. Credentials are the one part of the plugin model that is
per-workspace — the platform stores and injects them scoped to the calling workspace.
Polling triggers
A trigger starts workflow runs from external events. ABasePollingTrigger answers one
poll call on an interval and carries cursor state across polls; the platform keeps
owning scheduling, state persistence, and event-to-run routing.
Knowledge-base data sources
ABaseDataSource feeds documents into a Noxus knowledge base. Its fetch returns
Files (uploaded through the host callback), letting a KB ingest content from proprietary
systems the built-in sources don’t cover.
The plugin class
The core of a plugin is oneBasePlugin subclass that declares its components:
A V2 node
inputs; the rest is config. call
returns a flat dict keyed by the output-schema field names (list[X] fields are list
outputs). Configuration forms are rendered by the platform from your schema (the NCL
config language) — no frontend code required.
New work should be V2. V1 nodes remain supported for existing V1 flows, but V2 is
the model the platform builds around going forward.
The manifest is the contract
BasePlugin.get_manifest() reflects over your plugin class into a PluginManifest — the
single artifact the platform depends on. It carries:
The manifest is generated from code, not hand-written — regenerate it whenever a
component signature or config changes:
How plugins run
Your code never runs inside a platform process. Each plugin gets its own sandbox — its own filesystem, its ownuv virtual environment, its own resource limits — and the
platform starts a warm worker (noxus plugin worker) in it that imports your module
graph once and stays alive between calls. The platform reaches it over line-delimited
JSON-RPC.
A workflow hitting a plugin node dispatches node.execute (with the node name, a
serialized execution context, and the config) to the worker; the result comes back over
the same channel.
Files are host callbacks, not network calls
A jailed sandbox has no route back to internal platform addresses. When your node reads or writes aFile, the SDK issues a JSON-RPC callback on the same channel
(host.get_content / host.upload_file) that the platform services in-process against
its own database and storage. No platform credential ever enters the sandbox, and each
callback is scoped by a single-use token tied to the calling workspace — so a plugin
cannot reach another tenant’s files.
In call, use the execution context:
Isolation means
- Your plugin can use any Python dependencies without conflicting with the platform.
- A plugin crash or hang can’t take down the core platform.
- Plugins reach platform resources (files, credentials) only through the controlled callback surface.
Sandbox providers and browser/JIT plugins
The sandbox provider is a deployment setting (SANDBOX_PROVIDER), but it matters when
authoring. The default provider, syd, uses a seccomp filter that SIGSYS-kills V8’s
JIT — so any browser or JIT-heavy workload (Playwright, Chromium, a Node driver) dies on
startup, even when you only drive a remote browser and the local driver still needs to
run. Those plugins must run under the gvisor provider (Linux/EKS) or none
(local dev). Know this before shipping a browser-based plugin.
Deployment model
Plugins are deployment-global, not per-workspace: an org admin installs a plugin once and its components are available across the deployment. There is no per-workspace copy of a plugin. The only per-workspace piece is integration credentials — each workspace connects its own.Runtime gotchas
The warm-worker sandbox imposes rules the local API doesn’t. Keep these in mind:- stdout is the protocol. The worker speaks JSON-RPC on stdout; a stray
printcorrupts a call. Send logs to stderr, and wrap chatty dependencies withcontextlib.redirect_stdout(sys.stderr). - Import heavy deps lazily, inside
call. Manifest generation imports your module in a lightweight environment; a top-levelimport pandas/playwrightmakesget_manifest()fail withModuleNotFoundErroreven though the sandbox has the dependency. - Degrade, don’t raise, for expected external failures. A raised exception fails the
whole run. Catch flaky network/parsing errors and return an empty typed result; reserve
exceptions (
IntegrationFailedError) for genuine misconfiguration. - Zip only source. Exclude
.venv,__pycache__,.gitfrom the package; a stray.venvcan balloon the archive from KB to tens of MB. - Keep
noxus-sdk>=aligned with the platform. The sandbox resolvesnoxus-sdkalongside your deps; pinning an old floor can make the worker fail to start.
SDK and CLI
Thenoxus-sdk package provides the authoring surface:
noxus plugin serve runs a plugin as a local FastAPI app for development, but file
operations raise there — there is no local host to service the callbacks. Nodes that
read or write platform files can only be exercised under the platform runtime.Plugin lifecycle
1
Create
Scaffold with
noxus plugin create, or set up the package structure by hand.2
Develop
Implement nodes, integrations, triggers, and configuration.
3
Validate
Run
noxus plugin validate to reflect the manifest and check your definitions.4
Package & install
Push to Git or upload the
.tar.gz, then install from the Noxus UI. The platform
provisions the sandbox, installs your dependencies, and warms the worker.5
Operate
Monitor status, view worker logs, and update by restarting with a new source version.
Your First Plugin
Step-by-step tutorial from zero to deployed plugin
Configurable Plugins
Plugin-level config, dynamic node config, and validation